systemd service management cheat sheet

systemd is PID 1 on every mainstream distro, and two tools do nearly all the work: systemctl changes and inspects units, journalctl reads their logs. This sheet keeps the two side by side because the workflow is a loop — change something, then read what happened.

Mental model that prevents most confusion: start affects right now, enable affects the next boot — neither implies the other, and enable --now is the usual both-at-once. Unit suffixes (.service) are optional when unambiguous, so systemctl status nginx and systemctl status nginx.service are the same call.

systemctl: control and inspect units

Verified against systemd v250-era behavior; rows needing root are prefixed sudo. nginx is the running example — substitute any unit
TaskCommandNotes
Start a service nowsudo systemctl start nginx.servicereturns once started or failed; failures surface in status
Stop a servicesudo systemctl stop nginx.servicerunning processes receive SIGTERM, then SIGKILL after the timeout
Restart a servicesudo systemctl restart nginx.servicefull stop plus start — connections drop; prefer reload when offered
Reload config without restartsudo systemctl reload nginx.serviceruns the unit's ExecReload; errors if the unit defines none
Reload if supported, else restartsudo systemctl reload-or-restart nginx.serviceconvenient in scripts; check status afterwards to know which path ran
Status overviewsystemctl status nginx.serviceloaded/enabled state, MainPID, memory, and the last log lines
Is it running?systemctl is-active nginxprints active, inactive or failed; exit code works in scripts
Will it start at boot?systemctl is-enabled nginxenabled, disabled or static — see the list-unit-files row
Enable start at bootsudo systemctl enable nginxsymlinks into multi-user.target.wants; does not touch the running state
Enable and start togethersudo systemctl enable --now nginxthe standard post-install one-liner
Disable start at bootsudo systemctl disable nginxleaves a currently running instance alone
Disable and stop togethersudo systemctl disable --now nginxthe clean decommission
Refuse every start attemptsudo systemctl mask nginx.servicesymlinks the unit to /dev/null — stronger than disable, blocks manual starts too
Undo a masksudo systemctl unmask nginx.servicerestores normal startability
Loaded units on the systemsystemctl list-units --type=serviceshows loaded, active units; add --all for inactive ones too
Only failed unitssystemctl --failedthe first stop whenever something breaks mysteriously
Installed unit files and boot statesystemctl list-unit-files --type=servicestatic = no [Install] section, pulled in by other units rather than enabled directly
Show the effective unit filesystemctl cat nginxprints the original plus every drop-in override with file paths
Edit a drop-in overridesudo systemctl edit nginxopens an editor on /etc/systemd/system/nginx.service.d/override.conf; daemon-reloads on save — restart the unit yourself
Edit the full original unitsudo systemctl edit --full nginxcopies the vendor file to /etc/systemd/system; prefer overrides so upgrades still apply
Re-read unit files from disksudo systemctl daemon-reloadrequired after hand-editing units outside systemctl edit; restarts nothing
Clear a stale failed statesudo systemctl reset-failed nginxsilences the red failed banner after the actual cause is fixed
Query individual propertiessystemctl show nginx -p MainPID,ActiveStatecomma-separated property list, script-friendly single-line output
Show the boot targetsystemctl get-defaultmulti-user.target = server, graphical.target = desktop; set-default switches it
Scheduled timerssystemctl list-timerscron's systemd counterpart; NEXT/LAST columns expose drift

journalctl: read what happened

Reads run as your user if you are in the systemd-journal, adm or wheel group; otherwise sudo. Output pages through less by default
TaskCommandNotes
Logs for one unitjournalctl -u nginx.serviceoldest first; press G to jump to the end or add -e to start there
Follow one unit livejournalctl -f -u nginx.servicethe tail -f equivalent across the journal; Ctrl+C exits
Logs in a time windowjournalctl --since "2026-08-01 09:00" --until "2026-08-01 12:00"quoted timestamps; today, yesterday and "1 hour ago" work too
Errors and worsejournalctl -p errpriority err and more severe: emerg, alert, crit, err — cuts noise hard
Current boot onlyjournalctl -b-b -1 walks one reboot back — the post-crash question
Kernel messagesjournalctl -kthe dmesg equivalent, with proper timestamps
Combined triage viewjournalctl -xeu nginx.service-x extra explanation, -e end of log, -u unit filter — start debugging here
Pipe-friendly tailjournalctl -n 50 --no-pagerlast 50 lines straight to stdout, ready for grep or redirection
Journal disk footprintjournalctl --disk-usagesum of archived plus active journal files
Shrink the journal nowsudo journalctl --vacuum-size=200Malso --vacuum-time=2weeks; make limits permanent via journald.conf (see FAQ)
The journal indexes by every field, so unit, priority, boot and time combine freely: journalctl -u nginx -p warning --since yesterday is a valid, common compound. If logs vanish after reboot the journal is volatile — see the FAQ on /var/log/journal.

Anatomy of a unit file

A minimal, production-shaped service unit
[Unit]
Description=My API service
Wants=network-online.target
After=network-online.target

[Service]
Type=simple
User=api
WorkingDirectory=/opt/api
ExecStart=/usr/local/bin/api --port 8080
ExecReload=/bin/kill -HUP $MAINPID
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target

The directives that matter, by section

[Unit] Description=
human-readable name shown by status and list-units
[Unit] After= / Before=
pure ordering — start this unit before/after another; implies no dependency
[Unit] Wants= / Requires=
dependency strength: Wants pulls in but tolerates failure; Requires fails too when the dependency fails
[Service] Type=
simple (foreground, default), forking (daemonizes itself), oneshot (runs once), notify (signals readiness)
[Service] ExecStart= / ExecReload=
the start command; Reload is what systemctl reload triggers, conventionally a HUP via $MAINPID
[Service] Restart=on-failure
auto-restart on nonzero exit or signal; alternatives: always, no — plus RestartSec for the delay
[Service] User= / WorkingDirectory=
drop privileges and anchor relative paths; omitting User means root
[Install] WantedBy=multi-user.target
which target pulls the unit when enabled — multi-user for services, graphical adds display managers

FAQ

What is the difference between systemctl restart and reload?

restart stops the service completely and starts it fresh: all connections drop, caches rebuild, downtime equals startup time. reload asks the running process to re-read its configuration (via the unit's ExecReload directive) without stopping — sockets stay open. Reload only works if the unit defines ExecReload and the application supports live reconfiguration; nginx and sshd do, many apps do not. systemctl reload-or-restart tries reload and falls back to restart.

What is the difference between systemctl enable and start?

start acts on right now: launches the unit immediately and forgets it at reboot. enable acts on the next boot: creates a symlink in multi-user.target.wants so the unit starts automatically, touching nothing that is currently running. A freshly installed service typically needs enable --now — enable for future boots and start for this session in one command.

A service keeps failing — how do I debug it?

Three steps cover most cases. First systemctl status myapp.service: it shows the recent exit code and the last log lines inline. Then journalctl -xeu myapp.service for the full annotated story around the failure — -x adds explanations, -e jumps to the end. Common causes visible right there: bad paths (WorkingDirectory or ExecStart typos), permission problems from the User= account, or a port already held by another unit (ss -tulpn confirms). After fixing, systemctl reset-failed clears the stale state before restarting.

How do I shrink or limit journal disk usage?

Immediately: journalctl --disk-usage shows the current footprint, and sudo journalctl --vacuum-size=200M (or --vacuum-time=2weeks) trims it on the spot. Permanently: set SystemMaxUse=200M under the [Journal] section of /etc/systemd/journald.conf, then restart systemd-journald. Without a cap the journal grows toward 10% of the filesystem, which is rarely wanted on small volumes.

Why did my journalctl logs disappear after a reboot?

Because the journal was volatile — stored in /run/log/journal (a tmpfs) and wiped at boot. Persistence is controlled by journald's Storage setting (default auto): create /var/log/journal, run sudo systemctl restart systemd-journald, and logs survive reboots from then on. Most server distros ship persistent by default; containers and some minimal images do not.