A systemd timer is a unit of type .timer that activates another unit, usually a service, at a defined time or after a defined interval. In Linux system initialization and service management, timers are the native systemd mechanism for scheduled and delayed tasks, covering the work that cron and at traditionally handled.
Because a timer only triggers a unit, the scheduled job itself runs as an ordinary systemd service with its own logging, resource limits, dependencies and security settings. This separation of when from what is the defining design choice of systemd timers and the source of most of their advantages.
How a timer activates its service
A timer works as a pair of unit files. The file backup.timer holds the schedule, and by default it activates backup.service with the same base name. A different target can be named with the `Unit=` directive. When the timer elapses, systemd starts the service exactly as if an administrator had run systemctl start.
The timer is enabled and started like any other unit, typically with systemctl enable --now backup.timer, and it is pulled in at boot through `timers.target`. The service itself is usually not enabled, because it should run only when the timer fires, not at every boot.
If the service is still running when the timer elapses again, systemd does not start a second instance. This built-in protection against overlapping runs is something cron does not provide, where long jobs need external locking with tools such as flock.
Timers also work with template units. A file named snapshot@.timer can be enabled as snapshot@home.timer and snapshot@var.timer, and each instance activates the matching snapshot@.service instance with the instance name as a parameter. One pair of files then schedules the same job for many targets without duplication.
Monotonic and realtime timers
Systemd distinguishes monotonic timers, which count from an event such as boot, from realtime timers, which follow the wall clock and calendar. A single timer unit may combine several directives, and it fires whenever any of them elapses.
The following table lists the main schedule directives and what each one measures.
| Directive | Kind | Measured from | Example |
|---|---|---|---|
| OnBootSec= | Monotonic | System boot | OnBootSec=10min |
| OnStartupSec= | Monotonic | Start of the service manager | OnStartupSec=5min |
| OnActiveSec= | Monotonic | Activation of the timer | OnActiveSec=1h |
| OnUnitActiveSec= | Monotonic | Last activation of the target unit | OnUnitActiveSec=6h |
| OnUnitInactiveSec= | Monotonic | Last deactivation of the target unit | OnUnitInactiveSec=30min |
| OnCalendar= | Realtime | Calendar expression | OnCalendar=Mon..Fri 02:30 |
Calendar expressions use the format `DayOfWeek Year-Month-Day Hour:Minute:Second`, with shortcuts such as hourly, daily, weekly and monthly. The command `systemd-analyze calendar` parses an expression and prints the next elapse time, which makes schedule errors easy to catch before deployment.
Directives that control precision and catch-up
Several directives tune how exactly and how reliably a timer fires. They matter on laptops, on fleets of servers and for jobs that must not be skipped.
| Directive | Default | Effect |
|---|---|---|
| Persistent= | false | Runs a missed calendar job at next boot |
| AccuracySec= | 1min | Allows coalescing within this window to save power |
| RandomizedDelaySec= | 0 | Adds a random delay to spread load |
| WakeSystem= | false | Wakes a suspended machine to run the job |
| FixedRandomDelay= | false | Keeps the random offset stable per host |
Persistent=true stores the last trigger time on disk, so a daily job missed while the machine was off runs shortly after the next boot. This replaces the role of anacron on desktops. RandomizedDelaySec= is essential on large fleets, because hundreds of hosts running OnCalendar=daily would otherwise hit a shared repository at the same second after midnight.
For tasks that need second-level precision, AccuracySec=1s or lower must be set explicitly, because the default one-minute window lets systemd delay elapses to batch wakeups.
Systemd timers compared with cron
Timers and cron solve the same scheduling problem with different trade-offs. Cron remains simpler for a quick one-line job, while timers integrate scheduling into the service manager that already controls the rest of the system.
| Aspect | systemd timer | Cron |
|---|---|---|
| Configuration | Two unit files | One crontab line |
| Logging | Journal per unit, readable with journalctl | Mail or manual redirection |
| Overlap protection | Built in | Requires external locking |
| Missed runs | Caught up with Persistent=true | Lost unless anacron is used |
| Resource control | Cgroup limits, sandboxing | Only what the script sets up |
| Dependencies | Can order after mounts or network | None |
| Per-user jobs | User units with lingering | User crontabs |
The decisive advantage of timers is observability. systemctl list-timers shows every timer with its last and next run, and journalctl -u backup.service shows the complete output of each run with timestamps and exit codes, without any mail configuration.
Pitfalls when writing timer units
Most timer failures come from mismatched names, missing enablement or wrong service type. The following list covers the problems administrators encounter most often.
- Enabling the service instead of the timer: the job then runs at boot and never on schedule.
- Forgetting daemon-reload: after editing unit files,
systemctl daemon-reloadis required before changes take effect. - Using Type=simple for batch jobs:
Type=oneshotis the correct type for a task that runs to completion. - Relying on a user shell environment: services start with a minimal environment, so paths and variables must be set in the unit.
- User timers that stop at logout: per-user timers need
loginctl enable-lingerto run without an active session.
For one-off delayed tasks, `systemd-run –on-active=30min` creates a transient timer and service without writing any files, which is the systemd counterpart of the at command.
The simple explanation
A systemd timer is an alarm clock for the computer. You write down when the alarm should ring, such as every night at two, and what should happen when it rings, such as starting a backup. The computer takes care of the rest.
Unlike a simple alarm, this one also keeps a diary. It remembers whether the task ran, what it printed and whether it failed, and it can catch up on a missed alarm if the computer was switched off at the scheduled time.
How it is used in practice
An operations team runs a database backup on forty Linux servers. Under cron, all forty started at exactly 02:00, overloaded the shared storage and occasionally overlapped with the previous run, while failures went unnoticed because nobody read the cron mail.
The team replaces the cron line with db-backup.service of Type=oneshot and db-backup.timer containing OnCalendar=*-*-* 02:00, `RandomizedDelaySec=45min` and Persistent=true. A monitoring check reads the service result from systemd every morning.
Backups now spread across a 45-minute window, storage load stays flat, no run ever overlaps the previous one, and a failed backup appears immediately as a failed unit with its full log in the journal.