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?
answer
- two units, not one line
- the schedule and the work are separate
- name pairing has a default
- enable the .timer, never the .service
basics
~20 sA .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 ssystemd 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# /etc/systemd/system/backup.timer
[Unit]
Description=Nightly backup
[Timer]
OnCalendar=*-*-* 02:30:00
[Install]
WantedBy=timers.targetgo deeper
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.
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.
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.
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