A systemd timer can be enabled and still not run when you expect. The timer may be waiting for its next scheduled time, its service may be failing, or the schedule may behave differently after a machine has been offline.
Here’s a practical way to trace a timer from its schedule to the command it starts.
Start with the timer’s actual state
List timers systemd currently knows about:
systemctl list-timers --all
The output includes the last activation, the next activation, and the unit the timer activates. --all matters: without it, inactive timers are omitted.
For one timer, inspect its status and loaded configuration:
systemctl status backup.timer
systemctl cat backup.timer
Replace backup.timer with your unit name. Check whether it is loaded, active, and enabled—and whether systemctl cat shows the file you meant to configure. A timer can be active for a different unit than the one you’re editing.
If you need help interpreting the fields in systemctl status, see this guide to reading systemctl status output.
Confirm which service the timer starts
A timer typically activates a service with the same base name. For example, backup.timer activates backup.service by default.
The timer can also specify a different unit explicitly:
[Timer]
OnCalendar=*-*-* 02:30:00
Unit=nightly-backup.service
Inspect that service rather than assuming the timer’s name identifies it:
systemctl cat nightly-backup.service
systemctl status nightly-backup.service
A timer can be working perfectly while the service exits with an error. Check the service’s recent logs:
journalctl -u nightly-backup.service -n 50 --no-pager
For a broader look at filtering and following system logs, use this journalctl guide.
Check whether the schedule means what you think
OnCalendar= uses systemd’s calendar syntax. A daily run at 02:30 looks like this:
[Timer]
OnCalendar=*-*-* 02:30:00
Validate the expression without waiting for the timer:
systemd-analyze calendar '*-*-* 02:30:00'
This prints the next matching time. It’s a quick way to catch an expression that parses but doesn’t describe the schedule you intended.
A common surprise is the difference between calendar timers and monotonic timers. OnCalendar= refers to wall-clock time. OnBootSec= and OnUnitActiveSec= are relative to boot or a unit’s activation, respectively. They solve different scheduling problems; choosing the wrong one can make a timer appear to run too early, too late, or only after a reboot.
For example, a service that should run ten minutes after boot can use:
[Timer]
OnBootSec=10min
A service that should run an hour after its previous activation can use:
[Timer]
OnUnitActiveSec=1h
Decide what should happen after downtime
If a machine is powered off at the scheduled time, a regular calendar timer doesn’t necessarily run the missed job when the machine comes back.
For work that should catch up after downtime, add Persistent=true:
[Timer]
OnCalendar=*-*-* 02:30:00
Persistent=true
For a calendar timer, systemd records when it last triggered. If one or more scheduled times passed while it was inactive, persistence causes it to trigger after activation. It does not replay every missed occurrence; don’t treat it as a durable job queue.
This is useful for jobs such as a daily cleanup that should happen eventually. It may be inappropriate when every individual run matters or when running several jobs back-to-back would be harmful.
Apply changes and enable the right unit
After editing a unit file, ask systemd to reload unit definitions, then enable and start the timer:
sudo systemctl daemon-reload
sudo systemctl enable --now backup.timer
daemon-reload makes systemd reread unit files; it does not run the service or restart the timer’s target. This explanation of daemon-reload covers what it does and when it’s needed.
If you only changed the timer schedule, restarting the timer ensures the updated schedule is loaded:
sudo systemctl restart backup.timer
Then verify the next activation:
systemctl list-timers --all
Test the service independently
Don’t wait for the next scheduled time to find out whether the command works. Start the service directly:
sudo systemctl start backup.service
Then check its result and logs:
systemctl status backup.service
journalctl -u backup.service -n 50 --no-pager
If manual activation fails, investigate the service before changing the timer. Timer-started services don’t necessarily get the same environment as your interactive shell: a command available through your shell’s PATH, for instance, may need an absolute path in the unit’s ExecStart=.
A short diagnostic sequence
When a timer seems stuck, work through these checks in order:
-
systemctl list-timers --all— is it scheduled, and what unit does it activate? -
systemctl cat name.timer— does the loaded schedule match your file? -
systemd-analyze calendar 'EXPRESSION'— does the next run time make sense? -
systemctl status target.service— did the service start and finish successfully? -
journalctl -u target.service— what did the service report? - If downtime matters, decide whether
Persistent=truefits the job.
This separates scheduling problems from execution problems. A timer’s next activation tells you when systemd plans to start work; the service status and logs tell you whether that work succeeded.
I'm building SSHFlow — an SSH client that organizes terminals, SFTP, code editing, and server tools into workspaces.
Top comments (0)