Systemd: Services, Timers, and Journal
Systemd has become the default init system on nearly every major Linux distribution. It
manages service lifecycle, on-demand socket activation, scheduled tasks via timers,
device events, mount points, and system logging through its integrated journal. Mastering
systemd is non-negotiable for anyone administering Linux servers or workstations. This
guide covers everyday service management with systemctl, the anatomy of unit files,
creating custom services, timer units as a cron replacement, and log inspection with
journalctl.
Service Management with systemctl
The systemctl command is the primary interface for controlling systemd:
# Start, stop, restart, reload a service
systemctl start nginx
systemctl stop nginx
systemctl restart nginx # stop then start
systemctl reload nginx # reload config without full restart (if supported)
# Enable / disable at boot
systemctl enable nginx # create symlink in the target's wants directory
systemctl disable nginx # remove the symlink
systemctl enable --now nginx # enable AND start in one command
# Check status
systemctl status nginx # shows active state, recent logs, PID, memory
systemctl is-active nginx # exits 0 if running
systemctl is-enabled nginx # exits 0 if enabled
# List units
systemctl list-units --type=service # running services
systemctl list-units --type=service --state=failed # failed services
systemctl list-unit-files --type=service # all installed services
systemctl list-timers # all active timers
To inspect dependencies and ordering:
systemctl list-dependencies nginx.service
systemctl show nginx.service --property=After
Unit File Anatomy
Unit files typically live in /etc/systemd/system/ (admin overrides) or
/usr/lib/systemd/system/ (package defaults). They are INI-style files with three main
sections.
# /etc/systemd/system/myapp.service
[Unit]
Description=My Application Server
Documentation=https://example.com/docs
After=network-online.target postgresql.service
Wants=network-online.target
Requires=postgresql.service
[Service]
Type=simple
User=appuser
Group=appuser
WorkingDirectory=/opt/myapp
Environment=NODE_ENV=production
EnvironmentFile=/opt/myapp/.env
ExecStartPre=/opt/myapp/migrate.sh
ExecStart=/usr/bin/node /opt/myapp/server.js
ExecReload=/bin/kill -HUP $MAINPID
Restart=on-failure
RestartSec=5
StandardOutput=journal
StandardError=journal
LimitNOFILE=65536
[Install]
WantedBy=multi-user.target
Service Types
| Type | Behaviour |
|---|---|
simple |
systemd considers the service started as soon as ExecStart is forked. The process should NOT daemonize. This is the default and usually the right choice. |
forking |
The process daemonizes (forks and parent exits). Systemd waits for the parent to exit, then tracks the child. Use PIDFile= to help systemd find the main PID. |
oneshot |
The process runs to completion then exits. Systemd waits for it to finish. Combine with RemainAfterExit=yes to keep the unit "active" after exit. |
notify |
Like simple, but the process sends sd_notify("READY=1") when it is fully initialised. |
exec |
Like simple, but systemd waits for the binary to be executed (not just forked). |
After writing or changing a unit file, always reload:
systemctl daemon-reload
systemctl restart myapp.service
Timers as a Cron Replacement
Systemd timers offer several advantages over cron: dependency on other units, randomised delays to avoid thundering herd, persistent scheduling that catches up on missed runs, and full journal integration.
A timer unit is paired with a same-named service unit. For example:
# /etc/systemd/system/backup.timer
[Unit]
Description=Daily database backup timer
[Timer]
OnCalendar=*-*-* 02:30:00
Persistent=true
RandomizedDelaySec=300
[Install]
WantedBy=timers.target
# /etc/systemd/system/backup.service
[Unit]
Description=Database backup job
[Service]
Type=oneshot
User=backup
ExecStart=/opt/scripts/backup.sh
systemctl enable --now backup.timer
systemctl list-timers # verify next trigger time
journalctl -u backup.service # check past runs
OnCalendar uses a flexible syntax:
OnCalendar=hourly # every hour at :00
OnCalendar=daily # midnight
OnCalendar=Mon *-*-* 09:00:00 # every Monday at 9am
OnCalendar=*-*-01 06:00:00 # first of every month
Persistent=true means if the machine was off at the scheduled time, the timer fires
as soon as the system boots.
Journalctl: Querying the System Journal
The journal collects logs from all systemd units, the kernel, and any process writing to stdout/stderr under systemd supervision.
# Follow logs for a specific unit (like tail -f)
journalctl -u nginx.service -f
# Show logs since a timestamp
journalctl --since "2026-04-15 08:00" --until "2026-04-15 12:00"
journalctl --since "1 hour ago"
# Filter by priority (0=emerg ... 7=debug)
journalctl --priority=err # show err, crit, alert, emerg
journalctl -p warning -u myapp.service
# Kernel messages only (like dmesg)
journalctl -k
# Show disk usage and vacuum
journalctl --disk-usage
journalctl --vacuum-size=500M
journalctl --vacuum-time=30d
Targets
Targets are grouping units that replace traditional runlevels:
systemctl get-default # e.g., multi-user.target
systemctl set-default graphical.target
systemctl isolate rescue.target # switch to single-user rescue mode
Common targets: multi-user.target (text mode, networking), graphical.target
(desktop), rescue.target, emergency.target.
For deeper coverage of how processes are tracked and signalled, see Process Management. To understand how systemd orchestrates the early boot sequence, visit Boot Process.
Back to the Linux overview.