skip to content

Timers vs Cron

Timer units as systemd's answer to cron: OnCalendar= wall-clock schedules versus the OnBootSec= and OnUnitActiveSec= monotonic timers, AccuracySec= and WakeSystem=, and the paired .service they activate. Interviewers ask you to compare the two and want the real advantages named: journald logging, dependency ordering, and catching up on a missed run.

on this pageshow

questions

6

On a systemd host you add a backup.timer unit with an OnCalendar= schedule, but the backup never runs. How does a systemd timer unit actually get its work done, and what has to be in place for the schedule to be active?

level: juniorimportance: must knowfreq 68%

answer

  1. two units, not one line
  2. the schedule and the work are separate
  3. name pairing has a default
  4. enable the .timer, never the .service

basics

~20 s

A .timer unit only schedules; the work lives in a separate .service unit that it activates — by default the same-named one. You must enable and start the timer itself (systemctl enable --now backup.timer), not the service.

solid answer

~40 s

systemd splits a scheduled job into two units. `backup.timer` holds the schedule in its `[Timer]` section, and `backup.service` holds the actual command in `ExecStart=`. When the timer elapses it starts the service named by `Unit=`, which defaults to the timer's own name with a `.service` suffix — so `backup.timer` triggers `backup.service` with no extra configuration. The service is normally `Type=oneshot`: it runs, exits, and sits `inactive (dead)` until the next trigger, which is expected and not a failure. The piece people miss is activation: it is the **timer** you enable and start (`systemctl enable --now backup.timer`), not the service. If you enable the service instead, you have told systemd to run the job at boot and the schedule is still dormant. After editing unit files, run `systemctl daemon-reload`, then confirm with `systemctl list-timers`.

code

ini · 9 lines
ini
# /etc/systemd/system/backup.timer
[Unit]
Description=Nightly backup

[Timer]
OnCalendar=*-*-* 02:30:00

[Install]
WantedBy=timers.target

go deeper

for a junior

Be able to say plainly that a timer schedules and a service does the work, that the two are separate files, and that you enable the timer with systemctl enable --now.

for a middle

Explain the name-derivation rule for Unit=, why Type=oneshot leaves the unit inactive between runs, and how list-timers confirms which service a timer activates.

for a senior

Show the diagnostic order you use when a scheduled job silently does nothing, and explain why running the job via systemctl start reproduces the scheduled environment exactly.

for a principal

Argue the design tradeoff: two units per job costs boilerplate but makes every scheduled job a first-class supervised unit, which is what makes fleet-wide policy on scheduled work possible at all.

## Two units, one job Cron packs a schedule and a command onto a single line. systemd deliberately separates them. A **timer unit** (`*.timer`) is a scheduling object with a `[Timer]` section and nothing executable in it. A **service unit** (`*.service`) is the thing that actually runs a command via `ExecStart=`. The timer's only job is to start the service at the right moment. This feels like extra ceremony until you see what it buys: the job is an ordinary unit, so everything systemd can do to a service — ordering, resource limits, environment, cgroup accounting, log capture — applies to a scheduled job for free, and you can run the job by hand with `systemctl start backup.service` in exactly the environment it will get on schedule. ```ini # /etc/systemd/system/backup.timer [Unit] Description=Nightly backup [Timer] OnCalendar=*-*-* 02:30:00 [Install] WantedBy=timers.target ``` ## How the timer finds its service The `[Timer]` section supports `Unit=`, naming the unit to activate. If you omit it, systemd uses the timer's own name with the suffix replaced by `.service`. So `backup.timer` → `backup.service`, `certbot-renew.timer` → `certbot-renew.service`. This default is why most timer units in the wild have no `Unit=` line at all. The corollary is a very common deployment bug: if you name the units differently — `backup.timer` next to `nightly-backup.service` — the timer elapses and tries to start a `backup.service` that does not exist. Either match the names or set `Unit=nightly-backup.service` explicitly. ## The paired service ```ini # /etc/systemd/system/backup.service [Unit] Description=Nightly backup job [Service] Type=oneshot ExecStart=/usr/local/bin/backup.sh ``` `Type=oneshot` tells systemd the process is expected to run to completion and exit; the unit is considered activated only once the command finishes, and it reports `inactive (dead)` in between runs. Seeing `inactive (dead)` on a scheduled job is normal. Seeing `failed` means the last run exited non-zero. Note that this service has no `[Install]` section on purpose. You are not going to enable it — the timer is what pulls it in. ## Enabling the timer, not the service This is the single most frequent cause of "my timer never fires": ```bash systemctl daemon-reload systemctl enable --now backup.timer systemctl list-timers backup.timer ``` `enable` makes the timer come up on the next boot; `--now` also starts it in this boot so you do not have to reboot to arm it. Enabling `backup.service` instead does something quite different — it asks for the job to run at boot — and leaves the schedule inactive. `systemctl daemon-reload` matters whenever you add or edit unit files: systemd works from an in-memory view of the unit database and will otherwise keep using the old (or absent) definition. ## Verifying it took `systemctl list-timers` prints one row per active timer with the next elapse, the time remaining, the last elapse, and — in the `ACTIVATES` column — the service each timer will start. That last column is the fastest way to confirm the pairing resolved to what you intended. `systemctl status backup.timer` shows a `Trigger:` line with the next firing time, and confirms the timer unit is `active (waiting)`. ## The failure modes, in order of how often you hit them 1. The **service** was enabled and the timer was not, so nothing is scheduled. 2. The timer was created but never started; it will only arm after a reboot, or not at all if it was never enabled. 3. `daemon-reload` was skipped after writing the files. 4. The names do not pair and no `Unit=` was given. 5. A syntax error in `[Timer]` makes the unit fail to load — `systemctl status backup.timer` reports the bad setting. All five are visible in under a minute from `systemctl list-timers` plus `systemctl status` on both halves of the pair.

  • If you wanted backup.timer to trigger a service that is not called backup.service, how would you do it?
    Set `Unit=` in the `[Timer]` section to the exact unit name, for example `Unit=nightly-backup.service`. Without it, systemd derives the target from the timer's own name by swapping the `.timer` suffix for `.service`, so a mismatched pair silently tries to start a unit that does not exist.
  • How do you run a timer-driven job immediately, outside its schedule, without disturbing the timer?
    Run `systemctl start backup.service`. That activates the same unit the timer would, with the same environment, working directory, user and resource limits, so it is a real rehearsal rather than an approximation. The timer keeps its own schedule; its next elapse is unaffected.
  • Why does the paired service usually have no [Install] section?
    Because nothing should enable it. The timer is the unit you enable, and it is what pulls the service in when it elapses. Giving the service an `[Install]` section invites someone to enable it, which schedules the job at boot instead of on the timer's schedule.

saying these in an interview costs you the question

  • Puts ExecStart= inside the .timer unit file
  • Enables the .service and expects the schedule to run
  • Reads inactive (dead) between runs as a failure
  • Forgets systemctl daemon-reload after writing unit files
  • Assumes Unit= is mandatory in every timer

context

open as a page

Your team runs its nightly jobs from crontab entries and is considering moving them to systemd timer units. What do timers actually give you that cron does not, and what do you give up?

level: middleimportance: must knowfreq 70%

basics

~20 s

Timers make each job a supervised unit: output lands in the journal, exit status is recorded, the job runs in its own cgroup with resource limits, it can be ordered against other units, and systemctl list-timers shows every schedule on the host. The cost is two files per job and no portability off systemd.

open as a page

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?

level: middleimportance: should knowfreq 55%

basics

~20 s

OnCalendar= 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.

open as a page

A systemd timer with OnCalendar=daily did not fire because the machine was powered off at the scheduled time. What does Persistent=true in the [Timer] section change about that, and how does systemd know a run was missed?

level: middleimportance: should knowfreq 48%

basics

~20 s

Persistent=true makes systemd record each trigger as a timestamp file on disk. When the timer starts and that timestamp is older than the schedule allows, the job runs once immediately to catch up. It applies only to OnCalendar= schedules.

open as a page

A systemd timer you deployed is not firing when you expected. How do you check what systemd thinks your OnCalendar= expression means, when the timer last and next elapses, and whether the problem is in the timer or in the service it triggers?

level: seniorimportance: should knowfreq 45%

basics

~20 s

Normalize the schedule with systemd-analyze calendar, check arming and elapse times with systemctl list-timers --all and systemctl status on the .timer, then separate the halves: run the paired service by hand with systemctl start and read its status and journal.

open as a page

Four hundred servers each run the same systemd timer with OnCalendar=*-*-* 03:00:00, and every night they hit the same internal package mirror at once. Which [Timer] directives spread that load, and which one does not do what people expect?

level: seniorimportance: nice to knowfreq 38%

basics

~20 s

RandomizedDelaySec= is the spreader: it delays each trigger by a random amount between zero and the given span. AccuracySec= does not spread load across a fleet — it only widens the window in which systemd may fire a timer so it can batch wakeups on that one host.

open as a page