systemd is the init system and service manager on virtually every mainstream Linux
distribution today — Ubuntu, RHEL, Oracle Linux, Debian, SUSE. Love it or not, if you run
Linux in production you manage services through it, so it pays to know it well rather than
copy-pasting systemctl restart and hoping.
Units: the building blocks
systemd manages units, each described by a small INI-style file. The types you'll meet most often:
- .service — a daemon or one-shot process (nginx, postgresql, your app).
- .timer — scheduled activation, the modern replacement for many cron jobs.
- .mount — filesystem mounts managed as units.
- .target — grouping points, like
multi-user.target, roughly analogous to runlevels.
Vendor units live in /usr/lib/systemd/system/; your customisations belong in
/etc/systemd/system/, which takes precedence. Never edit vendor files directly —
use systemctl edit <unit> to create an override drop-in that survives
package upgrades.
The daily driver commands
$ systemctl status nginx # state, PID, recent log lines
$ systemctl restart nginx # stop + start
$ systemctl reload nginx # re-read config without dropping connections
$ systemctl enable --now nginx # start now AND on every boot
$ systemctl list-units --failed # anything broken right now?
The distinction between restart and reload matters in production:
a reload keeps existing connections alive where the service supports it. And
enable vs start trips up everyone once — start is for
now, enable is for boot. Forgetting enable is why services
mysteriously "disappear" after a maintenance-window reboot.
Logs with journald
systemd captures stdout/stderr of every unit into the journal. That means no more hunting for the right log file for basic service issues:
$ journalctl -u nginx -f # follow live, like tail -f
$ journalctl -u nginx --since "1 hour ago"
$ journalctl -p err -b # all errors since last boot
$ journalctl --disk-usage # how much space the journal uses
Writing your own service file
Any long-running application deserves a proper unit file instead of a
nohup ... & in someone's shell history. A solid template:
# /etc/systemd/system/myapp.service
[Unit]
Description=My Application
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=myapp
WorkingDirectory=/opt/myapp
ExecStart=/opt/myapp/bin/server --config /etc/myapp/config.yml
Restart=on-failure
RestartSec=5
NoNewPrivileges=true
ProtectSystem=full
[Install]
WantedBy=multi-user.target
A few deliberate choices in there: it runs as a dedicated non-root user,
Restart=on-failure gives you basic self-healing, and
NoNewPrivileges/ProtectSystem are free sandboxing wins. After
creating or changing a unit, always run systemctl daemon-reload before starting
it.
Timers instead of cron
Timers pair with a service of the same name and bring logging, dependency handling, and
Persistent=true (run missed jobs after downtime) — things cron never gave you:
# backup.timer
[Timer]
OnCalendar=*-*-* 02:30:00
Persistent=true
[Install]
WantedBy=timers.target
Check what's scheduled with systemctl list-timers. For anything that matters —
backups, certificate renewals, cleanup jobs — a timer is easier to audit than a crontab
scattered across user accounts.
Takeaway
systemd rewards the small investment of learning its vocabulary: units, targets, the journal, and drop-in overrides. Once your applications run as proper units with restart policies and journald logging, a whole category of "who started this process and where are its logs?" incidents simply stops happening.
back to all posts