Skip to content
Serverküche
Search

Loading search … (only available on the published site).

Basics Difficulty: Beginner

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.

· 6 min read ·Duration: approx. 30 minutes
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

🍳 Recommendation Ad

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.

Go to netcup →

💶 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:

Terminal
systemctl --version
Ausgabe
systemd 257 (257.13-1~deb13u1)

systemctl shows all running services:

Terminal
systemctl list-units --type=service --state=running
Ausgabe
  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 server

Each 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:

Terminal
systemctl status docker
Ausgabe
● 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:

Terminal
systemctl is-active docker
systemctl is-enabled docker
Ausgabe
active
enabled

The 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:

Terminal
systemctl enable --now SERVICE

enable 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:

Terminal
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 (like tail -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:

INI
[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:

Terminal
systemctl daemon-reload
systemctl start backup-demo.service

Check the result – oneshot services correctly show inactive (dead) after doing their work:

Terminal
systemctl status backup-demo.service
Ausgabe
○ 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):

INI
[Unit]
Description=Runs the demo backup daily

[Timer]
OnCalendar=*-*-* 03:00:00
Persistent=true

[Install]
WantedBy=timers.target

OnCalendar 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:

Terminal
systemctl daemon-reload
systemctl enable --now backup-demo.timer

All active timers and their next run are shown by:

Terminal
systemctl list-timers
Ausgabe
NEXT                        LEFT  UNIT                ACTIVATES
Sat 2026-07-25 03:00:00 CEST 7h   backup-demo.timer   backup-demo.service

Test OnCalendar expressions

Before activating a complicated schedule, check it with 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 run daemon-reload once and re-enable the 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 with systemctl status SERVICE like any other service.
  • Keep an eye on failed units. systemctl --failed lists services that have crashed – a quick health check for the system. For real alerting, Uptime Kuma tells you the server is down, while systemctl --failed shows the cause on the host.

You might also like

First steps with a netcup VPS
Basics Beginner

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 …

· 5 min read