A systemd timer unit can be scheduled with OnCalendar= or with monotonic directives such as OnBootSec= and OnUnitActiveSec=. What is the difference between the two kinds of schedule, and when would you pick a monotonic timer?
answer
- wall clock versus elapsed interval
- one of them follows DST
- what is the reference event?
- from start, or from finish?
- Persistent= only helps one family
basics
~20 sOnCalendar= fires at wall-clock times and follows the system clock, time zone and DST. Monotonic directives fire a fixed interval after a reference event — boot for OnBootSec=, the triggered unit's last activation for OnUnitActiveSec= — and ignore clock changes.
solid answer
~50 s`OnCalendar=` takes a calendar expression such as `Mon *-*-* 02:00:00` or a shorthand like `daily`, and elapses at that wall-clock moment in the system's local time zone unless the expression ends in `UTC`. That is what you want when the schedule has to line up with the outside world — a nightly window, an hourly report boundary, a business-hours job. Monotonic directives measure elapsed time from a reference event instead: `OnBootSec=` from boot, `OnStartupSec=` from service-manager start, `OnUnitActiveSec=` from the last time the triggered unit was activated, `OnUnitInactiveSec=` from the last time it went inactive. The classic idiom combines two of them — `OnBootSec=15min` with `OnUnitActiveSec=1h` — meaning "first run a quarter hour after boot, then every hour thereafter". Pick monotonic when you care about the interval rather than the clock face: it is immune to DST and clock steps, and it naturally staggers hosts because they boot at different times. Note that `Persistent=` only applies to calendar timers.
code
ini · 10 lines[Unit]
Description=Check for updates hourly after boot
[Timer]
OnBootSec=15min
OnUnitActiveSec=1h
Unit=update-check.service
[Install]
WantedBy=timers.targetgo deeper
Recall that OnCalendar= is a clock-time schedule and that OnBootSec=/OnUnitActiveSec= count elapsed time from an event, and be able to read a simple calendar expression.
Explain each directive's reference point, the OnBootSec= plus OnUnitActiveSec= priming idiom, and why Persistent= is meaningless for a monotonic timer.
Choose between the families for a real workload — clock alignment versus interval guarantees — and call out DST and time-zone exposure on calendar schedules before it becomes an incident.
Set the fleet convention: which jobs are allowed wall-clock schedules at all, whether hosts run in UTC, and how interval-based scheduling is used deliberately to avoid synchronized load.
## Two clocks, two questions Every schedule answers one of two questions. Either "at what time of day must this happen?" — which is a wall-clock question — or "how long after something else should this happen, and how often?" — which is an interval question. systemd gives each its own set of directives in the `[Timer]` section, and picking the wrong family is a design error rather than a syntax one. ## Calendar timers ```ini [Timer] OnCalendar=Mon *-*-* 02:00:00 ``` The expression has the shape `DayOfWeek Year-Month-Day Hour:Minute:Second`, with `*` as a wildcard, commas for lists, `..` for ranges and `/` for repetition (`*:0/15` is every fifteen minutes). Shorthands exist: `hourly`, `daily`, `weekly`, `monthly`, `yearly`, and `minutely`. `OnCalendar=daily` is exactly `*-*-* 00:00:00`. Calendar expressions are evaluated in the system's local time zone unless the expression ends with `UTC`. That single fact causes most calendar surprises. If the box is on a local zone with daylight saving, a `02:30` job may be skipped on the spring transition and run twice on the autumn one, and a fleet spread across zones will not fire simultaneously. If the schedule is really "02:30 in one specific place", say so in the expression or run the host in UTC and compute the offset once, deliberately. You can hand any expression to `systemd-analyze calendar` to see how systemd normalizes it and when it will next elapse — do that before deploying anything non-obvious. ## Monotonic timers ```ini [Timer] OnBootSec=15min OnUnitActiveSec=1h ``` Monotonic directives take a time span (`30s`, `15min`, `2h`, `1d`) measured from a reference point, and the reference is what distinguishes them: - **`OnBootSec=`** — relative to when the machine booted. - **`OnStartupSec=`** — relative to when the service manager itself started. For the system manager that is essentially boot; for a per-user manager (`systemctl --user`) it is when that user's manager started, which is the meaningful difference. - **`OnActiveSec=`** — relative to when the *timer unit* was activated. - **`OnUnitActiveSec=`** — relative to when the *triggered unit* was last activated. - **`OnUnitInactiveSec=`** — relative to when the triggered unit last went inactive. Multiple directives may appear together; the timer elapses at each computed point. The `OnBootSec=` plus `OnUnitActiveSec=` pairing is idiomatic precisely because a bare `OnUnitActiveSec=` has no reference point until the unit has run at least once — you need something to prime the first run. ## OnUnitActiveSec versus OnUnitInactiveSec The distinction matters for long jobs. `OnUnitActiveSec=1h` measures from the moment the job *started*, so a run that takes fifty minutes leaves only ten minutes of idle time before the next trigger. `OnUnitInactiveSec=1h` measures from the moment it *finished*, guaranteeing a full hour of quiet between runs. If the job's duration is variable and you care about not piling pressure on a downstream system, `OnUnitInactiveSec=` is usually the honest choice. ## Why monotonic is often the better default 1. **Immune to the clock.** NTP steps, manual `timedatectl` changes, DST transitions and time-zone edits do not shift a monotonic timer. A calendar timer will recompute against the new wall clock. 2. **Naturally staggered.** Fifty hosts that all boot at slightly different moments run an `OnBootSec=`-based job at slightly different moments. Fifty hosts with the same `OnCalendar=` fire together, which is how internal mirrors and metrics endpoints get hammered on the minute. 3. **Expresses the real requirement.** "Check for updates roughly every six hours" is an interval, not a clock time. Writing it as `*-*-* 0,6,12,18:00:00` invents a precision the requirement never had. ## Where monotonic falls down - It cannot express "the first Monday of the month" or "weekdays at 09:00". Business calendars are calendar timers. - **`Persistent=` only works with `OnCalendar=`.** There is no missed-run concept for a monotonic timer, because its reference point moves with the machine — after a week of downtime, `OnBootSec=` simply starts counting from the new boot. - On a machine that reboots frequently, an `OnBootSec=`-anchored job may run far more often than intended, since every boot restarts the count. ## Reading it back Whatever you write, `systemctl list-timers` shows the computed next elapse and, for monotonic timers, the same absolute time it derived — which is the quickest way to confirm that your interval means what you think it does.
- What is the practical difference between OnUnitActiveSec=1h and OnUnitInactiveSec=1h?`OnUnitActiveSec=` measures from when the triggered unit last started, so a fifty-minute run leaves only ten minutes of gap before the next trigger. `OnUnitInactiveSec=` measures from when it last went inactive, guaranteeing a full hour of quiet between runs. For jobs of variable duration that press on a shared downstream system, measure from the finish.
- Why does a bare OnUnitActiveSec= with no other directive tend not to fire at all?It is relative to the last activation of the triggered unit, so with no prior run there is no reference point to count from. You prime it with a second directive — typically `OnBootSec=` or `OnActiveSec=` — which produces the first run; from then on the interval directive takes over.
- A calendar timer set for 02:30 local time misbehaves twice a year. What is going on?Daylight-saving transitions. In the spring the local clock skips the 02:30 instant so the run is missed; in the autumn 02:30 occurs twice, and the schedule can fire on both. Either anchor the expression to UTC by suffixing `UTC`, run the host in UTC, or move the job outside the transition window.
saying these in an interview costs you the question
- Thinks OnCalendar= is interpreted in UTC by default
- Believes Persistent= rescues a missed monotonic timer
- Says monotonic timers drift with NTP corrections
- Confuses OnBootSec= with OnUnitActiveSec= as reference points
- Expresses 'roughly every six hours' as four fixed clock times