LinuxVII · systemd and Service ManagementTimers
systemd timers — replacing cron with calendar and monotonic schedules
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
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
$ systemctl daemon-reload; systemctl enable --now backup.timerIllustrative output
OnCalendar — real-time scheduling
The OnCalendar= directive uses the systemd time syntax
(described in man 7 systemd.time):
| Expression | Meaning |
|---|---|
*-*-* 02:00:00 | Every day at 02:00 |
Mon..Fri *-*-* 06:00:00 | Weekdays at 06:00 |
Sat,Sun *-*-* 00:00:00 | Weekends at midnight |
*-01-01 00:00:00 | January 1st every year |
*:0/15 | Every 15 minutes (any hour) |
2026-08-09 12:00:00 | Specific date and time |
$ 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 hoursIllustrative 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”:
| Directive | Trigger |
|---|---|
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
$ cat /etc/systemd/system/logrotate.timer[Unit]
Description=Daily rotation of log files
[Timer]
OnCalendar=daily
AccuracySec=1min
Persistent=true
[Install]
WantedBy=timers.targetIllustrative 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
$ systemctl list-timers --allNEXT 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
| Practice | Why |
|---|---|
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
Q1. What does `Persistent=true` do in a timer unit?
Q2. RandomizedDelaySec=15min randomises the actual activation within 15 minutes of the scheduled time.
Q3. Which of the following are correct timer practices? Select all that apply.
Passing score: 75%. Answers are checked in this browser.