Linux Administration
System Services and systemd
In this lesson
systemd is the init system and service manager used by virtually every major Linux distribution. It is the first process launched by the kernel at boot (PID 1), and it is responsible for starting, stopping, and supervising every service and daemon on the system. Understanding systemd means understanding how Linux itself comes to life — and how to control every running service with precision and confidence.
How systemd Works — Units and the Dependency Graph
systemd organises everything it manages into units — discrete, declarative configuration files that describe a service, a socket, a mount point, a timer, or a boot target. When the system starts, systemd reads all unit files, builds a dependency graph, and starts units in parallel wherever it can, making it significantly faster than older sequential init systems.
Fig 1 — systemd as PID 1 activating a target which starts units in parallel
A background process — daemon. The most common unit type. Covers nginx, sshd, cron, docker, and almost every service you will manage.
A synchronisation point that groups units together. Replaces the concept of SysV runlevels. Examples: multi-user.target, graphical.target.
A scheduled activation unit — the systemd replacement for cron. Activates a paired .service unit on a calendar or monotonic schedule.
Manages a filesystem mount point. Allows other units to declare dependencies on a mount being active before they start.
Activates a service on-demand when a connection arrives on a socket. The service only starts when actually needed — saving resources at idle.
systemctl — Controlling Services Day to Day
systemctl is the command-line interface to systemd. It is the single tool you use to start, stop, restart, enable, disable, and inspect every service on the system. Mastering its core subcommands covers the vast majority of real-world service management tasks.
| Command | What it does |
|---|---|
systemctl status nginx |
Show service state, PID, recent log lines, and whether it is enabled at boot. |
systemctl start nginx |
Start the service immediately. Does not affect whether it starts on next boot. |
systemctl stop nginx |
Stop the service immediately. Does not affect boot behaviour. |
systemctl restart nginx |
Stop then start the service. Drops all active connections briefly. |
systemctl reload nginx |
Ask the service to reload its configuration without restarting. Active connections are preserved. Not all services support this. |
systemctl enable nginx |
Configure the service to start automatically at boot. Does not start it right now. |
systemctl disable nginx |
Remove the service from boot startup. Does not stop a currently running instance. |
systemctl enable --now nginx |
Enable at boot and start immediately in one command — the most common real-world pattern. |
systemctl is-active nginx |
Returns active or inactive — useful in scripts for conditional logic. |
systemctl list-units --type=service |
List all loaded service units and their current states. |
# The most complete view of a service — always start here when troubleshooting
sudo systemctl status nginx
# Enable nginx at boot and start it right now (single command)
sudo systemctl enable --now nginx
# Reload config without dropping connections (nginx supports this)
sudo nginx -t && sudo systemctl reload nginx
# Restart a service (drops connections — use only when reload is not enough)
sudo systemctl restart nginx
# Check whether a service will start at boot
systemctl is-enabled nginx
# List all failed units — your first stop after a reboot with problems
systemctl list-units --state=failed
# List all active service units
systemctl list-units --type=service --state=active# sudo systemctl status nginx
● nginx.service - A high performance web server and a reverse proxy server
Loaded: loaded (/lib/systemd/system/nginx.service; enabled; vendor preset: enabled)
Active: active (running) since Wed 2025-03-12 09:15:42 UTC; 2h 14min ago
Docs: man:nginx(8)
Process: 1234 ExecStartPre=/usr/sbin/nginx -t -q -g daemon on; (code=exited, status=0/SUCCESS)
Main PID: 1235 (nginx)
Tasks: 3 (limit: 4611)
Memory: 6.2M
CPU: 87ms
CGroup: /system.slice/nginx.service
├─1235 "nginx: master process /usr/sbin/nginx -g daemon on;"
└─1236 "nginx: worker process"
Mar 12 09:15:42 server1 systemd[1]: Starting nginx...
Mar 12 09:15:42 server1 systemd[1]: Started nginx.
What just happened? systemctl status showed the full picture in one view: the unit file location, whether it is enabled at boot (enabled), the current runtime state (active (running)), the main PID and process tree, memory and CPU usage, and the most recent log lines — all without needing to open a separate log file.
Analogy: enable and start are completely separate concerns. enable sets the alarm clock — it determines whether the service wakes up when the system boots. start wakes it up right now regardless of the alarm. You need both: a service that is started but not enabled will disappear after the next reboot.
Understanding and Writing Unit Files
Every service systemd manages is described by a unit file — a plain text INI-style file with three sections. Distribution-provided unit files live in /lib/systemd/system/. Administrator overrides and custom services go in /etc/systemd/system/, which takes precedence. Never edit files in /lib/systemd/system/ directly — they will be overwritten by package upgrades.
# /etc/systemd/system/myapp.service
# A minimal but complete custom service unit file
[Unit]
Description=My Application Server
Documentation=https://myapp.example.com/docs
# Start only after the network is fully up
After=network-online.target
Wants=network-online.target
[Service]
# Type=simple means the ExecStart process IS the main service process
Type=simple
User=myapp
Group=myapp
WorkingDirectory=/opt/myapp
# The command that starts the service
ExecStart=/opt/myapp/bin/server --config /etc/myapp/config.yml
# Automatically restart if the process exits unexpectedly
Restart=on-failure
RestartSec=5s
# Security hardening — limit what the service can access
NoNewPrivileges=true
PrivateTmp=true
# Output goes to the journal
StandardOutput=journal
StandardError=journal
[Install]
# This unit is "wanted by" multi-user.target — i.e. starts at normal boot
WantedBy=multi-user.target# After creating or modifying a unit file, always reload the systemd daemon
sudo systemctl daemon-reload
# Then enable and start the new service
sudo systemctl enable --now myapp
# View the unit file systemd is actually using for a service
systemctl cat nginx
# Edit a unit file safely — creates a drop-in override, never touches the original
sudo systemctl edit nginx
# View all drop-in overrides for a unit
systemctl cat nginx
ls /etc/systemd/system/nginx.service.d/
# Verify a unit file has no syntax errors
systemd-analyze verify /etc/systemd/system/myapp.service# systemctl cat nginx # /lib/systemd/system/nginx.service [Unit] Description=A high performance web server and a reverse proxy server Documentation=man:nginx(8) After=network-online.target remote-fs.target nss-lookup.target Wants=network-online.target [Service] Type=forking PIDFile=/run/nginx.pid ExecStartPre=/usr/sbin/nginx -t -q -g 'daemon on; master_process on;' ExecStart=/usr/sbin/nginx -g 'daemon on; master_process on;' ExecReload=/bin/kill -s HUP $MAINPID KillSignal=SIGQUIT TimeoutStopSec=5 KillMode=mixed PrivateTmp=true [Install] WantedBy=multi-user.target
What just happened? systemctl cat printed the full unit file as systemd sees it, including any drop-in overrides stacked on top. This is the correct way to inspect a unit — reading the raw file in /lib/systemd/system/ directly may miss overrides that change the service's behaviour.
journalctl — Reading the systemd Journal
systemd captures all service output in a centralised structured log called the journal. journalctl is the tool for querying it. Because the journal is structured and indexed, it is far faster and more flexible to query than grepping through plain text log files.
The most frequently used flag. Shows only journal entries from the named service: journalctl -u nginx
Like tail -f but for the journal. Combine with -u to watch a specific service live: journalctl -fu nginx
Accepts human-readable time: --since "1 hour ago", --since "2025-03-12 09:00", --since today
Levels: emerg, alert, crit, err, warning, notice, info, debug. Shows specified level and above.
-b 0 = current boot, -b -1 = previous boot. Essential for diagnosing crash and reboot scenarios.
# View logs for nginx — most recent entries first
journalctl -u nginx -r
# Follow nginx logs in real time (like tail -f)
journalctl -fu nginx
# Show nginx logs from the last 30 minutes
journalctl -u nginx --since "30 minutes ago"
# Show only errors and above across the entire system
journalctl -p err
# Show all logs from the current boot — useful after an unexpected reboot
journalctl -b 0
# Show logs from the previous boot — diagnose a crash
journalctl -b -1
# Show logs between two specific times
journalctl -u nginx --since "2025-03-12 08:00" --until "2025-03-12 10:00"
# Check how much disk space the journal is using
journalctl --disk-usage# journalctl -u nginx --since "5 minutes ago" Mar 12 11:42:07 server1 systemd[1]: Reloading nginx... Mar 12 11:42:07 server1 nginx[8821]: 2025/03/12 11:42:07 [notice] 1235#1235: signal process started Mar 12 11:42:07 server1 systemd[1]: Reloaded nginx. # journalctl -p err -b 0 Mar 12 09:14:55 server1 kernel: EXT4-fs error (device sda1): ext4_find_entry:1455 Mar 12 09:15:01 server1 sshd[992]: error: Could not load host key: /etc/ssh/ssh_host_ed25519_key # journalctl --disk-usage Archived and active journals take up 128.0M in the file system.
What just happened? journalctl -p err -b 0 surfaced two important errors from the current boot — a filesystem error on sda1 and a missing SSH host key. Both would have been invisible in a standard systemctl status view. Filtering by priority level is one of the fastest ways to triage a system after an incident.
Targets — systemd's Replacement for Runlevels
Older Linux systems used SysV runlevels (numbers 0–6) to define system states. systemd replaces these with targets — named units that group other units together and define what the system should be doing. Targets are more descriptive and composable than runlevels.
SysV Runlevels (legacy)
0
Halt
1
Single-user mode
3
Multi-user, no GUI
5
Multi-user with GUI
6
Reboot
systemd Targets (modern)
poweroff.target
Halt the system
rescue.target
Single-user recovery
multi-user.target
Multi-user, no GUI
graphical.target
Multi-user with GUI
reboot.target
Reboot the system
# Check the current default boot target
systemctl get-default
# Change the default boot target (e.g. remove GUI on a server)
sudo systemctl set-default multi-user.target
# Switch to a target immediately without rebooting
sudo systemctl isolate rescue.target
# View all available targets
systemctl list-units --type=target
# Power management via systemctl
sudo systemctl poweroff
sudo systemctl reboot
sudo systemctl suspend# systemctl get-default multi-user.target # sudo systemctl set-default multi-user.target Removed "/etc/systemd/system/default.target". Created symlink /etc/systemd/system/default.target → /lib/systemd/system/multi-user.target. # systemctl list-units --type=target --state=active UNIT LOAD ACTIVE SUB DESCRIPTION basic.target loaded active active Basic System cryptsetup.target loaded active active Local Encrypted Volumes getty.target loaded active active Login Prompts multi-user.target loaded active active Multi-User System network-online.target loaded active active Network is Online network.target loaded active active Network
What just happened? set-default works by creating a symlink at /etc/systemd/system/default.target pointing to the chosen target. This file in /etc/systemd/system/ takes precedence over the distribution default — it is the admin override mechanism at work.
Troubleshooting Failed Services — A Systematic Approach
When a service fails to start, a consistent sequence of checks resolves the vast majority of problems within minutes. Following this order prevents the common trap of changing multiple things at once and losing track of what fixed the problem.
Step 1 — Check the service status
The status output shows the last few log lines and the exit code. Often this is enough to identify the problem immediately.
sudo systemctl status myapp.service
Step 2 — Read the full journal for this service
The status view is truncated. The full journal shows the complete error output including stack traces and config parse errors.
journalctl -u myapp.service --since "5 minutes ago"
Step 3 — Verify the unit file and run the binary manually
Check the unit file for typos with systemctl cat, then try running the ExecStart command directly in a terminal to see raw output.
systemctl cat myapp.service
sudo -u myapp /opt/myapp/bin/server --config /etc/myapp/config.yml
Step 4 — Fix, reload daemon, restart, confirm
After making a fix, always reload the daemon before restarting the service, then confirm it reached the running state.
sudo systemctl daemon-reload
sudo systemctl restart myapp.service
sudo systemctl status myapp.service
Always Run systemctl daemon-reload After Editing a Unit File
systemd caches unit file contents in memory. If you edit a unit file in /etc/systemd/system/ and then run systemctl restart without first running systemctl daemon-reload, systemd will restart the service using the old cached unit definition — your changes will have no effect. This is the source of much confusion when debugging custom services.
Lesson Checklist
start/stop (runtime) and enable/disable (boot behaviour), and I use enable --now to do both at once
[Unit], [Service], and [Install] sections and always run daemon-reload after any change
journalctl using unit filters, time ranges, and priority levels to find relevant log entries quickly
Teacher's Note
The single most underused journalctl invocation is journalctl -p err -b 0 — errors from the current boot across the whole system. Run this immediately after any unexpected reboot or service outage and it will almost always surface the root cause within the first few lines.
Practice Questions
1. You deploy a custom application and create a unit file at /etc/systemd/system/myapp.service. Write every command you would run, in order, to: load the unit, start the service, confirm it is running, and ensure it survives a reboot.
sudo systemctl daemon-reload (load the new unit file) → sudo systemctl start myapp (start it now) → sudo systemctl status myapp (confirm it is active/running) → sudo systemctl enable myapp (create the symlink so it starts on every boot).
2. A service called webapp.service shows as failed in systemctl list-units. Describe your step-by-step troubleshooting approach, naming the specific commands you would run and what information you expect to find at each step.
sudo systemctl status webapp — shows the exit code and last few log lines. Step 2 — sudo journalctl -u webapp -n 50 — full log output revealing the error. Step 3 — sudo systemctl cat webapp — review the unit file for misconfiguration. Step 4 — fix the issue, then sudo systemctl daemon-reload if the unit file changed, followed by sudo systemctl restart webapp and confirm with systemctl status webapp.
3. Explain the difference between systemctl reload nginx and systemctl restart nginx. In a production environment serving live traffic, which would you prefer after editing nginx.conf, and what command would you run first to make it safe?
reload signals the process to re-read its configuration without stopping — active connections are not dropped. restart fully stops then restarts the service, dropping all active connections. In production, prefer reload. First run sudo nginx -t to validate the config — if it passes, then sudo systemctl reload nginx.
Lesson Quiz
1. You run sudo systemctl enable nginx. What is the immediate effect on the running system?
2. You edit /etc/systemd/system/myapp.service to change the Restart= value, then immediately run sudo systemctl restart myapp. Why might your change have no effect?
3. Which journalctl command would you run to see only critical errors and above from the previous boot session?
Up Next
Lesson 15 — Process Management
Viewing, controlling, and prioritising running processes with ps, top, kill, and nice