Timers and operations

Create a service and timer

Run scheduled server work as a paired oneshot service and timer with separate logs and state.

8 minute lesson

~~~

A systemd timer activates another unit, normally a service with the same base name. This is systemd’s answer to cron, and the split into two files is the point: the service says what to run, the timer says when. You can run the service by hand any time, and swap the schedule without touching the command.

Compared to a crontab line, you also get the job’s output in the journal, real dependencies, resource limits, and catch-up behavior for machines that were off.

The service half

Put the command in a Type=oneshot service:

# /etc/systemd/system/backup.service
[Unit]
Description=Nightly PostgreSQL backup

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

No [Install] section. The service is never enabled on its own; the timer owns the schedule.

The timer half

The schedule goes in a timer using OnCalendar= (or monotonic settings like OnUnitActiveSec= for “every N minutes after the last run”):

# /etc/systemd/system/backup.timer
[Unit]
Description=Run backup.service every night

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

[Install]
WantedBy=timers.target

Enable the timer, not the service:

sudo systemctl daemon-reload
sudo systemctl enable --now backup.timer
systemctl list-timers backup.timer
NEXT                        LEFT     LAST PASSED UNIT         ACTIVATES
Tue 2026-08-04 03:00:00 UTC 9h left  -    -      backup.timer backup.service

list-timers is your proof: it shows the next elapse, and after the first run, when it last fired.

The classic mistake is enabling backup.service instead of backup.timer. The backup then runs once at every boot and never on schedule, and list-timers shows nothing — which is exactly how you spot it.

Check the schedule expression

Calendar expressions are easy to get subtly wrong. systemd-analyze calendar parses one and shows the next elapse:

systemd-analyze calendar "*-*-* 03:00:00"

Design backup.service and backup.timer as above without running a real backup — point ExecStart= at /bin/true first. Verify both files load, check list-timers, and confirm the schedule with systemd-analyze calendar.

Lesson completed

Take this course offline

Get every free book and course as PDF and EPUB files.

Get the download library →