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.