2 min readlinuxsystemd

Replacing cron jobs with systemd timers

Cron is fine until a job fails silently at 3 a.m. and you find out a week later. systemd timers need slightly more setup, but every run is logged in the journal, a run missed during downtime can be caught up, and jobs can depend on the network or a mounted disk.

Two files

A timer is a pair: a .service that does the work and a .timer that decides when.

# /etc/systemd/system/backup.service
[Unit]
Description=Nightly backup of /srv
Wants=network-online.target
After=network-online.target

[Service]
Type=oneshot
ExecStart=/usr/local/bin/backup.sh
Nice=10
IOSchedulingClass=idle
# /etc/systemd/system/backup.timer
[Unit]
Description=Run backup.service every night

[Timer]
OnCalendar=*-*-* 03:30:00
RandomizedDelaySec=15min
Persistent=true

[Install]
WantedBy=timers.target
systemctl daemon-reload
systemctl enable --now backup.timer

Enable the timer, not the service. The service has no [Install] section on purpose.

The useful options

  • Persistent=true — if the machine was off at 03:30, the job runs once at the next boot.
  • RandomizedDelaySec — spreads load when many machines share the same schedule.
  • OnBootSec= / OnUnitActiveSec= — relative schedules, e.g. "10 minutes after boot, then every hour".
  • AccuracySec= — defaults to one minute; lower it only if you really need precision.

Checking the schedule

Calendar expressions are easy to get wrong. systemd-analyze shows exactly when they will fire:

$ systemd-analyze calendar --iterations=3 'Mon..Fri 09:00'
  Original form: Mon..Fri 09:00
Normalized form: Mon..Fri *-*-* 09:00:00
    Next elapse: Thu 2026-03-12 09:00:00 UTC
       Iter. #2: Fri 2026-03-13 09:00:00 UTC
       Iter. #3: Mon 2026-03-16 09:00:00 UTC

And for everything that is scheduled on the machine:

systemctl list-timers --all

Logs and failures

journalctl -u backup.service --since yesterday
systemctl status backup.service

A failed run leaves the service in the failed state, which shows up in systemctl --failed and in most monitoring agents. To get notified, add OnFailure=notify@%n.service to the service and write a small template unit that sends a message.

When I still use cron

For a one-line job on a machine I do not maintain, or inside a minimal container without systemd, cron is still the simpler tool. Everywhere else, timers have replaced it for me.