Skip to content

systemd Service Management

systemd is the init system and service manager on every modern Linux distribution. This guide covers controlling services, reading logs, writing your own units, and replacing cron with timers.

Tested on

AlmaLinux 9 / RHEL 9, but systemctl and journalctl behave identically on Debian/Ubuntu.

Controlling services with systemctl

sudo systemctl start nginx      # start now
sudo systemctl stop nginx       # stop now
sudo systemctl restart nginx    # stop + start
sudo systemctl reload nginx     # reload config without dropping connections
sudo systemctl status nginx     # current state + recent log lines

Enable vs. start

These are independent concepts that trip people up:

  • start — run the service right now (does not survive reboot)
  • enable — start it automatically at boot (does not run it now)
sudo systemctl enable nginx          # start at boot, later
sudo systemctl enable --now nginx    # enable AND start now (the usual one)
sudo systemctl disable --now nginx   # disable AND stop now

Is it running? Will it start at boot?

systemctl is-active nginx     # active / inactive / failed
systemctl is-enabled nginx    # enabled / disabled / static

Find and inspect units

systemctl list-units --type=service              # currently loaded services
systemctl list-units --type=service --state=failed   # what's broken
systemctl list-unit-files --type=service         # all installed, with enable state
systemctl cat nginx                              # show the effective unit file(s)

Reading logs with journalctl

journalctl queries the systemd journal — structured, indexed logs for every unit.

journalctl -u nginx                  # all logs for the nginx unit
journalctl -u nginx -f               # follow live (like tail -f)
journalctl -u nginx -n 100           # last 100 lines
journalctl -u nginx --since "1 hour ago"
journalctl -u nginx -p err           # only error priority and worse
journalctl -u nginx -b               # only this boot

Triage a failed service fast

systemctl status myapp           # the failure summary + last few lines
journalctl -u myapp -e --no-pager   # jump to the end of its full log

Useful priority levels for -p: emerg, alert, crit, err, warning, notice, info, debug.

Writing your own service unit

Suppose you have an app at /opt/myapp/run that should run as a dedicated user and restart on failure. Create /etc/systemd/system/myapp.service:

[Unit]
Description=My Application
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
User=myapp
Group=myapp
WorkingDirectory=/opt/myapp
ExecStart=/opt/myapp/run
Restart=on-failure
RestartSec=5s
# Light sandboxing — cheap wins
NoNewPrivileges=true
ProtectSystem=full
ProtectHome=true

[Install]
WantedBy=multi-user.target

After creating or editing any unit file, reload the systemd manager so it picks up the change:

sudo systemctl daemon-reload
sudo systemctl enable --now myapp
systemctl status myapp

daemon-reload is mandatory

systemd caches unit files in memory. Edits to .service files have no effect until you run sudo systemctl daemon-reload.

Override a unit without editing it

Don't edit vendor-shipped units in /usr/lib/systemd/system/ — they get overwritten on package upgrade. Use a drop-in instead:

sudo systemctl edit nginx

This opens an editor for /etc/systemd/system/nginx.service.d/override.conf, where you add only the directives you want to change:

[Service]
LimitNOFILE=65535

Scheduling work with timers

systemd timers are a modern replacement for cron, with the advantage of full journald logging and dependency handling.

Create the job as a normal service, /etc/systemd/system/backup.service:

[Unit]
Description=Nightly backup

[Service]
Type=oneshot
ExecStart=/usr/local/bin/backup.sh

Then a timer, /etc/systemd/system/backup.timer:

[Unit]
Description=Run nightly backup at 02:30

[Timer]
OnCalendar=*-*-* 02:30:00
Persistent=true          # run on next boot if the machine was off at 02:30
RandomizedDelaySec=300   # spread load — fire within 5 min of the target

[Install]
WantedBy=timers.target

Enable the timer (not the service):

sudo systemctl daemon-reload
sudo systemctl enable --now backup.timer
systemctl list-timers           # see next run time for all timers

Validate an OnCalendar expression

systemd-analyze calendar "*-*-* 02:30:00"
# shows the normalized form and the next elapse time

Verify your work

systemctl is-active myapp && echo "running"
systemctl is-enabled myapp && echo "starts at boot"
systemd-analyze verify /etc/systemd/system/myapp.service   # lint the unit
systemctl list-timers --all

Summary

  • enable = at boot, start = now; enable --now does both.
  • Use journalctl -u <unit> -f / -e to follow and triage logs.
  • Put custom units in /etc/systemd/system/, override vendor units with systemctl edit, and always daemon-reload after changes.
  • Prefer systemd timers over cron for logged, dependency-aware scheduling.

Test yourself