Understanding systemd: Units, Journal & Timers (instead of Cron)
Start services, read logs, schedule tasks: this foundational guide explains systemd with real commands – including your own service and a timer instead of cron.
Table of contents
Every service on your server – SSH, Docker, the firewall – is started, supervised and logged by systemd. Understanding systemd lets you diagnose outages, create your own background jobs and automate scheduled tasks cleanly. This guide gets you comfortable with it.
What are we building?
Not a service, but the tool every service runs on: systemd (here v257 on Debian 13). By the end you can start, stop and enable services at boot, read their logs with journalctl, create your own service as a unit file and build a timer that replaces cron – with the key advantage that it can catch up on missed runs.
Prerequisites
- A Linux server with systemd (any modern Debian/Ubuntu – your netcup VPS)
- Root or
sudoaccess (see Users & permissions) - Familiarity with the essential terminal commands
VPS 1000 G12
4 vCores · 8 GB RAM · 256 GB NVMe
from €10.36/month
The commands are the same on any systemd Linux – any server will do.
💶 5 € voucher for new netcup customers:36nc17844976032
(new customers only, no domains)
Step by step
Step 1: Units – the core concept
systemd manages everything as units. The most important kind is the service (.service) – a background daemon. There are also timers (.timer, scheduled execution), sockets and targets (groups, roughly comparable to runlevels). First check which version is running:
systemctl --versionsystemd 257 (257.13-1~deb13u1)systemctl shows all running services:
systemctl list-units --type=service --state=running containerd.service loaded active running containerd container runtime
cron.service loaded active running Regular background program processing daemon
docker.service loaded active running Docker Application Container Engine
fail2ban.service loaded active running Fail2Ban Service
ssh.service loaded active running OpenBSD Secure Shell serverEach line is a unit with load state (loaded), active state (active) and sub-state (running).
Step 2: Controlling services
The most important command is systemctl status. It shows you everything about a service – using Docker as an example:
systemctl status docker● docker.service - Docker Application Container Engine
Loaded: loaded (/usr/lib/systemd/system/docker.service; enabled; preset: enabled)
Active: active (running) since Sun 2026-07-19 21:34:42 CEST; 4 days ago
Main PID: 53379 (dockerd)
Tasks: 93
Memory: 282.8M (peak: 334.6M)Two lines matter: Active: (is the service running right now?) and, in the Loaded: line, the word enabled (does it start automatically at boot?). These are two independent things – a service can run but not start at boot, and vice versa. For scripts you query both concisely:
systemctl is-active docker
systemctl is-enabled dockeractive
enabledThe four everyday commands: systemctl start SERVICE (start now), stop (stop now), restart (restart, e.g. after a config change) and reload (re-read the config without interrupting the service – if it supports that). Important: start/stop only last until the next reboot. For a service to permanently start at boot, you need enable:
systemctl enable --now SERVICEenable turns on autostart, --now additionally starts the service right away – saving you the separate start.
Step 3: Reading logs with journalctl
systemd collects the output of all services centrally in the journal. Instead of hunting through scattered log files, you query a specific service:
journalctl -u docker -n 20-u selects the unit, -n 20 shows the last 20 lines. The most useful variants:
journalctl -u SERVICE -f– follow live (liketail -f), ideal for debugging.journalctl -u SERVICE --since "1 hour ago"– narrow by time.journalctl -p err -b– only errors since the last boot (-b= current boot).
This is exactly the first move when a service won’t start: systemctl status SERVICE for the overview, then journalctl -u SERVICE for the details.
Step 4: Creating your own service
Now you build a unit yourself. Example: a backup script for systemd to run. Unit files for your own services live in /etc/systemd/system/. Create backup-demo.service:
[Unit]
Description=Demo backup job
[Service]
Type=oneshot
ExecStart=/usr/bin/bash -c 'echo "Backup ran at $(date)" >> /var/log/backup-demo.log'The [Unit] section describes the unit, [Service] says what should run. Type=oneshot is the type for one-off tasks that start, do their work and exit (unlike long-running daemons like docker). After creating a new unit, systemd has to read it in:
systemctl daemon-reload
systemctl start backup-demo.serviceCheck the result – oneshot services correctly show inactive (dead) after doing their work:
systemctl status backup-demo.service○ backup-demo.service - Demo backup job
Loaded: loaded (/etc/systemd/system/backup-demo.service; static)
Active: inactive (dead)
systemd[1]: Starting backup-demo.service - Demo backup job...
systemd[1]: backup-demo.service: Deactivated successfully.
systemd[1]: Finished backup-demo.service - Demo backup job.Step 5: Timers instead of cron
To make the job run regularly, you couple it to a timer – the systemd alternative to cron. Create backup-demo.timer (same name as the service, different extension):
[Unit]
Description=Runs the demo backup daily
[Timer]
OnCalendar=*-*-* 03:00:00
Persistent=true
[Install]
WantedBy=timers.targetOnCalendar sets the schedule (here: daily at 03:00). Persistent=true is the key advantage over cron: if the server was off at the scheduled time, systemd catches up on the run at the next start – so a missed backup doesn’t simply get skipped. Enable the timer:
systemctl daemon-reload
systemctl enable --now backup-demo.timerAll active timers and their next run are shown by:
systemctl list-timersNEXT LEFT UNIT ACTIVATES
Sat 2026-07-25 03:00:00 CEST 7h backup-demo.timer backup-demo.serviceTest OnCalendar expressions
systemd-analyze calendar "Mon *-*-* 09:00:00" – this shows the next execution times without starting anything. That way you avoid timers that never fire or fire at the wrong time.This exact pattern – a oneshot service plus timer – is what Serverküche’s Restic backups use. You now hold the tool for any recurring maintenance task.
When things go wrong
systemctl start fails, the service is immediately dead again. Read the details with journalctl -u SERVICE -n 30. For your own units the most common cause is a wrong path in ExecStart – the command must use an absolute path (/usr/bin/bash, not bash).
“Unit changed on disk, run daemon-reload”. You edited a unit file, but systemd doesn’t pick up changes automatically. After every change under /etc/systemd/system/: systemctl daemon-reload.
The timer doesn’t show up in list-timers. It wasn’t enabled. systemctl enable --now SERVICE.timer – and remember the timer triggers the service, so you need both files (.service and .timer).
enable says “static”. The unit is missing the [Install] section with WantedBy=. Without it systemd doesn’t know when to start the unit automatically – for timers it needs WantedBy=timers.target.
The journal is huge / eats disk. By default it grows in bounds, but you can trim it: journalctl --vacuum-time=14d deletes entries older than 14 days, journalctl --disk-usage shows the consumption.
Maintenance & backups
- Your own units belong in the backup. Everything under
/etc/systemd/system/is your own configuration and should be in your Restic backup. After a server rebuild you restore your services and timers with it instantly (then rundaemon-reloadonce and re-enablethe timers). - Timers instead of your own cron jobs. If you’re using systemd anyway, timers are the more consistent choice: they show up in the journal, catch up on missed runs (
Persistent=true) and can be monitored withsystemctl status SERVICElike any other service. - Keep an eye on failed units.
systemctl --failedlists services that have crashed – a quick health check for the system. For real alerting, Uptime Kuma tells you the server is down, whilesystemctl --failedshows the cause on the host.
Send feedback: feedback@serverkueche.de
You might also like

Understanding Linux Users, Groups & File Permissions
Who is allowed to do what on your server? This foundational guide explains users, groups and file permissions with real …

Understanding Docker Networks: bridge, internal DNS & the proxy network
Why does every app recipe declare a proxy network? This guide explains Docker networks, internal DNS and container …

First steps with a netcup VPS
From a freshly ordered netcup server to a ready-to-use system: SSH login, system updates, a sudo user and the most …