systemd Timer File Syntax#
A systemd timer is a unit file with the .timer extension that controls when an associated service runs. Timers replace traditional cron jobs and offer richer scheduling, dependency handling, and logging via the journal.
A timer typically pairs with a service of the same name. For example, backup.timer activates backup.service. If you want a different name, set Unit= explicitly in the [Timer] section.
Basic Structure#
A timer file has three sections:
[Unit]
Description=Human-readable description of what this timer does
Documentation=man:my-service(8)
[Timer]
# Scheduling directives go here
OnCalendar=daily
Persistent=true
Unit=my-service.service
[Install]
WantedBy=timers.target
The [Unit] section describes the timer. The [Timer] section defines when it fires. The [Install] section tells systemd how to enable it — timers.target is the standard target that pulls in all enabled timers at boot.
Key Timer Directives#
OnCalendar= triggers at a wall-clock time using a calendar expression. This is the most common directive and the closest analog to cron.
OnBootSec= triggers a fixed duration after the system boots.
OnStartupSec= triggers a fixed duration after systemd itself starts (usually the same as boot for the system manager, but distinct for user managers).
OnUnitActiveSec= triggers relative to the last time the associated unit was activated — useful for "run every N minutes after the last run finished."
OnUnitInactiveSec= triggers relative to the last time the unit deactivated.
Persistent=true causes a missed run to fire as soon as possible after the system comes back up — essential for machines that aren't always on.
RandomizedDelaySec= adds a random delay up to the given value, which is useful for spreading load across a fleet of machines.
AccuracySec= controls how loosely systemd may schedule the timer (default 1 minute). Lower values cost more wakeups; higher values save power.
Unit= overrides the default same-name service association.
Calendar Expression Format#
OnCalendar= uses the format DayOfWeek Year-Month-Day Hour:Minute:Second. Any field can be * (any value), a comma-separated list, a range with .., or a step with /. Shortcuts like daily, hourly, weekly, monthly, yearly, and minutely are also accepted. You can validate any expression with systemd-analyze calendar "your-expression".
Five Examples#
1. Daily at 3:30 AM with catch-up for missed runs#
[Unit]
Description=Nightly database backup
[Timer]
OnCalendar=*-*-* 03:30:00
Persistent=true
Unit=db-backup.service
[Install]
WantedBy=timers.target
This fires every day at 3:30 AM. If the machine was off at 3:30, Persistent=true runs it as soon as the system is up again.
2. Every 15 minutes, starting 5 minutes after boot#
[Unit]
Description=Poll external API for new messages
[Timer]
OnBootSec=5min
OnUnitActiveSec=15min
Unit=poll-api.service
[Install]
WantedBy=timers.target
Combining OnBootSec= with OnUnitActiveSec= is the standard idiom for "run shortly after boot, then every N minutes thereafter." The timer measures the 15-minute gap from when the unit last activated, so slow runs don't stack.
3. Weekdays only, multiple times per day#
[Unit]
Description=Sync inventory during business hours
[Timer]
OnCalendar=Mon..Fri 09,12,15,18:00:00
Unit=inventory-sync.service
[Install]
WantedBy=timers.target
This runs at 9 AM, noon, 3 PM, and 6 PM on Monday through Friday. Comma lists work in any field, and .. defines an inclusive range.
4. First day of every month with a randomized delay#
[Unit]
Description=Monthly billing report
[Timer]
OnCalendar=monthly
RandomizedDelaySec=30min
Persistent=true
Unit=billing-report.service
[Install]
WantedBy=timers.target
monthly is shorthand for *-*-01 00:00:00. The randomized delay scatters the actual start time across a 30-minute window — helpful when many machines share the same schedule and you want to avoid a thundering herd against a downstream service.
5. Every five minutes, all day, with relaxed accuracy#
[Unit]
Description=Lightweight health probe
[Timer]
OnCalendar=*:0/5
AccuracySec=30s
Unit=health-probe.service
[Install]
WantedBy=timers.target
The 0/5 step means "starting at minute 0, every 5 minutes" — so :00, :05, :10, and so on. AccuracySec=30s tells systemd it may fire up to 30 seconds late if that lets it batch wakeups with other timers, which is friendlier to laptops and battery-powered devices.
Enabling and Inspecting Timers#
Drop the .timer and matching .service files into /etc/systemd/system/, then run systemctl daemon-reload, systemctl enable --now my.timer. Inspect schedules with systemctl list-timers (next fire time, last fire time, associated unit) and test calendar strings without scheduling them using systemd-analyze calendar "Mon..Fri 09:00".