Every Linux service on a modern distribution starts, stops, restarts, logs, and runs on a schedule through one program. Learn systemctl and journalctl and you can operate almost any Linux box, whatever runs on it.
This guide covers the 20% of systemd you’ll use every day, then walks through writing a service unit and replacing a cron job with a timer. The examples were checked with systemd-analyze verify on systemd 252.
What You’ll Learn
- What units are, and the four types worth knowing.
- The
systemctlcommands you’ll run daily. - How to read logs with
journalctlwhen a service misbehaves. - How to write a service unit and verify it before it runs.
- How to replace a cron job with a timer.
- How to change a vendor unit without editing its file.
The Basics
systemd is the init system on most Linux distributions: the first process the kernel starts, and the parent of everything else. It replaced a pile of shell scripts with one declarative format and one set of commands. It attracts criticism for pulling much of the boot and service surface into a single project, but it’s what ships, so it’s what you operate.
A unit is one thing systemd manages, described by a small INI-style file. Four types cover most work:
.service: a process to run, such asnginx.service..timer: a schedule that starts another unit, the cron replacement..socket: a port or socket that starts a service on first connection..target: a group of units, such asmulti-user.target.
Units live in three places, and the first match wins:
/etc/systemd/system/for your units and overrides./run/systemd/system/for runtime units./usr/lib/systemd/system/for units shipped by packages. Don’t edit these.
Primary Use Cases
- Starting, stopping, and supervising services, including automatic restarts.
- Running scheduled work with timers.
- Collecting and querying logs through the journal.
Less Suitable Use Cases
- Orchestrating containers across several machines. Use Kubernetes or Nomad.
- Application-level job queues that need retries, backoff, and visibility.
When to Use systemd
Use systemd when a Linux machine must keep something running, start it at boot, run it on a schedule, or tell you why it stopped.
Everyday systemctl Commands
# What is running, what failed
systemctl list-units --type=service --state=running
systemctl --failed
# One service
systemctl status nginx
sudo systemctl start nginx
sudo systemctl stop nginx
sudo systemctl restart nginx
sudo systemctl reload nginx # re-read config without dropping connections
# Start now and at boot, in one command
sudo systemctl enable --now nginx
sudo systemctl disable --now nginx
# Questions that answer with an exit code, for scripts
systemctl is-active nginx
systemctl is-enabled nginx
# See the actual unit file and any overrides
systemctl cat nginx
# After editing a unit file by hand
sudo systemctl daemon-reloadTwo that save real time:
# Block a unit from starting at all, even as a dependency
sudo systemctl mask apache2
# What is installed, whether or not it is running
systemctl list-unit-files --type=servicerestart stops and starts the process. reload asks the service to re-read its configuration and only works if the unit defines ExecReload. systemctl cat prints the unit plus every drop-in that modifies it, which is the fastest way to see what a machine actually runs.
Read Logs With journalctl
journalctl -u nginx # everything for one unit
journalctl -u nginx -b # this boot only
journalctl -u nginx -f # follow, like tail -f
journalctl -u nginx --since "30 min ago"
journalctl -p err -b # errors and worse, this boot
journalctl -xeu nginx # jump to the end, with explanations
journalctl -k # kernel messages
# How much disk the journal uses, and how to shrink it
journalctl --disk-usage
sudo journalctl --vacuum-time=14djournalctl -xeu <unit> is the command to reach for when a service won’t start: the -e jumps to the end, the -x adds explanatory text, and -u keeps it to the unit you care about.
Write a Service Unit
A unit that runs one script and exits, such as a backup:
# /etc/systemd/system/backup.service
[Unit]
Description=Back up /srv to object storage
After=network-online.target
Wants=network-online.target
[Service]
Type=oneshot
ExecStart=/usr/local/bin/backup.sh
User=backup
Nice=10
[Install]
WantedBy=multi-user.targetWhat each key does:
Type=oneshottells systemd the process runs to completion instead of staying up. Long-running daemons useType=simple(the default) orType=notify.After=andWants=network-online.targetdelay the job until the network is up.After=sets the order,Wants=pulls the dependency in.User=runs the job as an unprivileged account.WantedBy=multi-user.targetis whatsystemctl enablehooks into.
Check the file before you trust it:
systemd-analyze verify /etc/systemd/system/backup.serviceSilence means the unit is valid. A typo gets named:
/etc/systemd/system/bad.service:8: Unknown key 'ExecStrt' in section [Service], ignoring.
bad.service: Service has no ExecStart=, ExecStop=, or SuccessAction=. Refusing.
Unit bad.service has a bad unit file setting.Then load and run it:
sudo systemctl daemon-reload
sudo systemctl start backup.service
systemctl status backup.serviceReplace a cron Job With a Timer
A timer is a unit that starts another unit on a schedule. It shares the same logs, the same status output, and the same failure handling as everything else.
# /etc/systemd/system/backup.timer
[Unit]
Description=Run backup.service every night
[Timer]
OnCalendar=*-*-* 03:00:00
RandomizedDelaySec=15m
Persistent=true
[Install]
WantedBy=timers.targetsudo systemctl enable --now backup.timer
systemctl list-timers --allThe three keys that matter:
OnCalendarsets the schedule. Check an expression before committing to it:
systemd-analyze calendar "Mon *-*-* 04:00:00"Normalized form: Mon *-*-* 04:00:00
Next elapse: Mon 2026-09-21 04:00:00 UTC
From now: 1 day 1h leftRandomizedDelaySecspreads the start time, so fifty machines don’t hit the same endpoint at 03:00:00.Persistent=trueruns a missed job once the machine comes back, which cron can’t do.
Timers beat cron for anything that matters: the run’s output lands in the journal, systemctl status shows the last result, and a failure can trigger another unit.
Change a Vendor Unit Without Editing It
Package upgrades overwrite files in /usr/lib/systemd/system/. Use a drop-in instead:
sudo systemctl edit nginxThat opens an empty override where you set only what you’re changing:
[Service]
Restart=always
RestartSec=5The result lands in /etc/systemd/system/nginx.service.d/override.conf and survives upgrades. systemctl cat nginx shows the vendor unit and your override together.
Get Told When Something Fails
A failed unit is silent unless you ask to hear about it. Attach a handler with OnFailure:
# /etc/systemd/system/backup.service.d/override.conf
[Unit]
OnFailure=status-email@%n.serviceHow to Send System Email to Gmail From Linux has the matching status-email@.service unit and the Gmail setup it needs.
Troubleshoot a Service
systemctl --failed # start here
journalctl -xeu backup.service # why it failed
systemd-analyze blame # what made boot slow
systemd-analyze critical-chain # the dependency path that took the time
systemctl list-dependencies nginx # what it waits onWhen a unit refuses to start, work in this order: systemctl status for the exit code, journalctl -xeu for the error, systemctl cat to confirm the unit is what you think it is, and systemd-analyze verify if you edited it.
Learn systemd: Beyond the Basics
- systemd.io: the project’s own documentation, including the container interface.
- systemctl(1) and journalctl(1): the manual pages, worth skimming once end to end.
- systemd.time(7): every calendar and timespan format a timer accepts.
- Arch Wiki: systemd: the most practical single page on the subject, useful on any distribution.
Related Content
- How to Send System Email to Gmail From Linux routes the alerts these units produce to an inbox you read.
- How to Fix Permission Denied (publickey) in SSH and GitHub covers the other setup every new Linux box needs.

Comments #