Skip to main content
RunBook Academy

LinuxVII · systemd and Service ManagementTimers

systemd timers — replacing cron with calendar and monotonic schedules

Intermediate⏱ ~10 minbashsystemctlsystemd-analyze

What you'll learn

  • Distinguish real-time and monotonic timer schedules
  • Write a calendar timer (OnCalendar=) for a recurring task
  • Write a monotonic timer (OnBootSec=, OnUnitActiveSec=) for delay-after-event tasks
  • Use Persistent=true for catch-up after downtime

Prerequisites

Verified against Ubuntu 24.04 LTS · Debian 12 (Bookworm) · RHEL 9.x · Rocky Linux 9.x · AlmaLinux 9.x · Linux kernel 6.1 LTS / 6.6 LTS · systemd 255+ · OpenSSH 8.7p1 (RHEL 9) / 9.6p1 (Ubuntu 24.04) · nftables 1.0.x · chrony 4.x · Pacemaker 2.1.x · Corosync 3.1.x · 2026-08-09

Not yet marked complete on this device.

systemd timers are the modern replacement for cron. They integrate with the dependency graph, log to the journal, support calendar arithmetic and monotonic schedules, and handle downtime gracefully.

Timer + service pair

A timer unit has the same name as a service unit, with .timer suffix. When the timer fires, systemd activates the matching service.

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

[Service]
Type=oneshot
ExecStart=/usr/local/bin/backup.sh
# /etc/systemd/system/backup.timer
[Unit]
Description=Nightly backup timer

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

[Install]
WantedBy=timers.target
Service impact possibleenable timer
$ systemctl daemon-reload; systemctl enable --now backup.timer

Illustrative output

OnCalendar — real-time scheduling

The OnCalendar= directive uses the systemd time syntax (described in man 7 systemd.time):

ExpressionMeaning
*-*-* 02:00:00Every day at 02:00
Mon..Fri *-*-* 06:00:00Weekdays at 06:00
Sat,Sun *-*-* 00:00:00Weekends at midnight
*-01-01 00:00:00January 1st every year
*:0/15Every 15 minutes (any hour)
2026-08-09 12:00:00Specific date and time
Read-only / Safesystemd-analyze calendar
$ systemd-analyze calendar '*-*-* 02:00:00'
Original form: *-*-* 02:00:00
Normalized form: *-*-* 02:00:00
Next elapse: Mon 2026-08-11 02:00:00 UTC
(in UTC): Mon 2026-08-11 02:00:00 UTC
From now: 1 day 13 hours

Illustrative output

Monotonic schedules

Monotonic schedules trigger after an event, not at a specific time. Useful for “do this N seconds after boot” or “N seconds since this service last ran”:

DirectiveTrigger
OnBootSec=N seconds after system boot
OnStartupSec=N seconds after systemd startup
OnActiveSec=N seconds after the timer itself activated
OnUnitActiveSec=N seconds after the matching service’s last activation
OnUnitInactiveSec=N seconds after the matching service’s last deactivation
[Timer]
OnBootSec=5min
OnUnitInactiveSec=30min

This fires 5 minutes after boot, then every 30 minutes after the last service deactivation. Useful for housekeeping tasks that should run shortly after boot and at regular intervals thereafter.

Persistent — catch-up after downtime

Read-only / Safepersistent timer
$ cat /etc/systemd/system/logrotate.timer
[Unit]
Description=Daily rotation of log files

[Timer]
OnCalendar=daily
AccuracySec=1min
Persistent=true

[Install]
WantedBy=timers.target

Illustrative output

RandomizedDelaySec — avoiding the thundering herd

A shared maintenance window across a fleet — every host running logrotate at exactly 02:00:00 — concentrates load on central services. RandomizedDelaySec=15min randomises the actual activation within a 15-minute window after the scheduled time.

[Timer]
OnCalendar=*-*-* 02:00:00
RandomizedDelaySec=15min

This is the default for many vendor timers on modern distributions. Adopt it for your own fleet-wide timers.

Listing and inspecting timers

Read-only / Safelist-timers
$ systemctl list-timers --all
NEXT                         LEFT     LAST                         PASSED   UNIT                         ACTIVATES
Mon 2026-08-11 02:00:00 UTC  1d 13h   Mon 2026-08-09 02:00:12 UTC  12h ago  logrotate.timer              logrotate.service
Mon 2026-08-11 00:00:00 UTC  1d 11h   Mon 2026-08-09 02:00:12 UTC  12h ago  backup.timer                 backup.service
Mon 2026-08-11 00:30:00 UTC  1d 12h   n/a                          n/a      fstrim.timer                 fstrim.service
...

Illustrative output

Production discipline

PracticeWhy
Use OnCalendar= for wall-clock schedules; OnUnitActiveSec= for delay-after-event.Cron only does wall-clock; systemd timers handle both natively.
Add RandomizedDelaySec= to fleet-wide timers.Avoids thundering-herd at top of hour.
Use Persistent=true only for idempotent jobs, and never to backfill per-day artefacts.Catch-up fires the unit once, however many runs were missed - one duplicate for a non-idempotent job, and no backfill at all for a job that owes one artefact per day.
Validate every expression with systemd-analyze calendar.Typos produce silent failures.
Add WantedBy=timers.target so the timer is enabled at boot.Without it, the timer is inactive after a reboot.

Knowledge check

Knowledge check · 3 questions

  1. Q1. What does `Persistent=true` do in a timer unit?

  2. Q2. RandomizedDelaySec=15min randomises the actual activation within 15 minutes of the scheduled time.

  3. Q3. Which of the following are correct timer practices? Select all that apply.

Passing score: 75%. Answers are checked in this browser.