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