systemd timers I keep reaching for
Cron is fine. It has been fine for decades. But once a host runs more than a few periodic jobs, the trade-off shifts: cron output disappears into mail (or /dev/null), failures are silent, and missed runs after a reboot are lost forever. systemd timers fix all three.
the minimal pair
A timer is two units: a .service describing the work, and a .timer describing when. For a nightly database backup:
# /etc/systemd/system/db-backup.service
[Unit]
Description=Nightly Postgres backup
[Service]
Type=oneshot
ExecStart=/usr/local/bin/backup-pg.sh
User=postgres
Nice=10
IOSchedulingClass=best-effort
IOSchedulingPriority=7
# /etc/systemd/system/db-backup.timer
[Unit]
Description=Run nightly Postgres backup
[Timer]
OnCalendar=*-*-* 03:17:00
RandomizedDelaySec=10m
Persistent=true
[Install]
WantedBy=timers.target
Three things to notice. OnCalendar uses a systemd.time(7) spec — no cron syntax to mis-remember. RandomizedDelaySec jitters the start time, which matters once you have a fleet. Persistent=true means a missed run (e.g. host was down at 03:17) will execute on next boot.
observability you get for free
systemctl status db-backup.timershows the next firing time.systemctl list-timerslists all timers, sorted by next-run.journalctl -u db-backup.servicegives you logs, scoped to that unit, with timestamps.- If the service exits non-zero, you get a failure state. You can wire that to an
OnFailure=handler — typically another oneshot that pages you.
when I still reach for cron
Two cases. First, environments that aren't systemd — Alpine containers, BSDs, anything restricted. Second, a single ad-hoc cleanup script that nobody is going to maintain. The friction of two unit files isn't worth it for a one-line job.
Everything else: timer.