Skip to content
← Guides

Your systemd timer didn't run, and nothing told you.

systemd timers are a popular replacement for cron on Linux, for backups, scripts and scheduled AI agents. But when a timer doesn't fire, or its service fails, systemd writes it to the journal and tells no one. Here are the usual causes, straight from the systemd documentation, and how to make the next missed run reach you.

Short answer

Run systemctl list-timers --all and check that your timer is listed with a NEXT time, then read journalctl -u <name>.service for the last run. Then make the job prove it ran: give it a watch in Did It Run? and end your script with a request to the watch's ping URL. If the timer doesn't fire or the script dies, no ping arrives and you get a missed alert at the scheduled time plus your grace window, from outside the machine. Start free (3 watches, no credit card).

Check-ins are by ping URL today, with /start and /fail variants (/start marks a run as begun; it doesn't count as a finished run). Email check-ins are coming soon.

Why a systemd timer doesn't run

  • The timer was never started, or not enabled for boot. A timer only fires while it is active. Creating backup.timer does nothing until you run systemctl enable --now backup.timer; without enable (and a WantedBy=timers.target line under [Install]), it won't come back after a reboot. If it isn't in systemctl list-timers --all, it isn't scheduled.
  • The machine was off at the scheduled time, and Persistent= isn't set. systemd only catches up on runs missed while the timer was inactive (for example, while the machine was powered down) when Persistent=true. It defaults to false and only applies to OnCalendar= timers (systemd.timer, Persistent=).
  • The machine was asleep. A calendar timer that comes due during suspend runs once the system resumes, and several missed runs during one sleep collapse into a single run. The timer only wakes the machine if WakeSystem=true is set and the hardware supports it (systemd.timer). A run that happens hours late can be as bad as no run.
  • The previous run was still going. If the service is still active when the timer comes due, systemd doesn't start a second copy; it leaves the running one alone (systemd.timer). A hung script quietly swallows every run after it.
  • It's a user timer and you logged out. A timer under systemctl --user runs in your user manager. Lingering (loginctl enable-linger) is what starts a user manager at boot and keeps it around after logout (loginctl). Without it, a user timer on a server can stop when your SSH session ends.
  • It ran later than you think. AccuracySec= defaults to 1 minute, and RandomizedDelaySec= (0 by default) adds a random delay on top when you set it, so a timer doesn't fire on the exact second (systemd.timer). Leave room for this in any deadline.
  • The timer fired, but the service failed. The service unit goes into a failed state and the error goes to the journal. Nothing is emailed or posted anywhere.

Step 1: read what systemd recorded

  1. systemctl list-timers --all shows each timer's NEXT and LAST run. No row, or no NEXT time, means it isn't scheduled.
  2. systemctl status backup.timer shows whether the timer is enabled and active.
  3. systemctl status backup.service and journalctl -u backup.service show the last run's exit status and output.
  4. For a user timer, add --user to the commands above and check loginctl show-user $USER --property=Linger.

Step 2: add a heartbeat so a missed run reaches you

  1. Create a watch in Did It Run? with the same schedule as the timer, in the same timezone, with a grace window that covers the job's normal run time plus any RandomizedDelaySec=.
  2. Point the service at a script that checks in when it finishes, and calls /fail when it errors:
ini
# /etc/systemd/system/backup.service
[Service]
Type=oneshot
ExecStart=/opt/jobs/backup-with-checkin.sh
bash
#!/usr/bin/env bash
PING="https://diditrun.app/p/<your-token>"
if /opt/jobs/backup.sh; then
  curl -fsS -m 10 --retry 3 "$PING" > /dev/null
else
  status=$?
  curl -fsS -m 10 --retry 3 -X POST --data-binary "backup.sh exited with status $status" "$PING/fail" > /dev/null
  exit "$status"
fi

Now each case is covered:

  • The timer didn't fire (not enabled, machine off, asleep, user logged out, last run still going): no ping arrives, so the run is missed at the deadline.
  • The script ran and failed: it calls /fail and you get a failed alert. The alert shows the first 200 characters of the text you send.
  • The script ran, but on nothing: send a count (?items=$count) and set an output sanity check on the watch; see Scheduled job ran but output was empty.

If the job could hang, also see Cron job or AI agent hung with no error. Still on cron? See Script works by hand but not from cron.

Honest limits

  • Did It Run? doesn't connect to your server or read the journal. It only sees the check-ins your script sends.
  • A missed alert tells you the run didn't arrive, not why. Step 1 is how you find out which cause it was.
  • If the machine has no internet when the script finishes, the check-in can't arrive and the run shows up as missed, which is the safe way round.

What you'll see

  • Missed: no check-in by the expected time plus grace.
  • Late: an optional early warning while the run is still inside its grace window.
  • Failed: the script called /fail.
  • Empty: the check-in arrived but failed the watch's sanity check.
  • Recovered: the first good run after a problem.

Free covers 3 watches with email alerts; Solo ($15/month) adds Slack, Discord and Telegram alerts and a daily roll-call digest across all your automations.

FAQ

Why isn't my systemd timer running?

Most often it was never started or enabled. Run systemctl list-timers --all; if the timer isn't listed with a NEXT time, run systemctl enable --now <name>.timer. If it is listed, check journalctl -u <name>.service to see whether the service ran and failed.

Does a systemd timer run a job it missed while the machine was off?

Only if the timer has Persistent=true, which defaults to false and only applies to OnCalendar= timers. With it on, the service is triggered as soon as the timer is active again if a run was due while it was inactive.

Why did my systemd timer skip a run while the last one was still going?

If the service is still active when the timer comes due, systemd leaves it running and doesn't start another copy. A hung job therefore blocks every run after it until it ends.

Why does my user timer stop when I log out?

User timers run in your user manager. Enable lingering with loginctl enable-linger so a user manager is started at boot and kept around after logout.

Can systemd alert me when a timer doesn't run?

systemd records runs and failures in the journal but doesn't send you an alert for a run that never happened. An outside heartbeat watch that expects a check-in on schedule catches it, because a run that never happens sends nothing.

Can I check in by email instead of a URL?

Not yet. Email check-ins are coming soon. Today every check-in is an HTTP request to the watch's ping URL, with /start and /fail variants.

Know the next time it doesn't run

Add one ping URL to the end of your job. If the ping doesn't arrive on time, Did It Run? emails you.

Free for 3 watches. No credit card.

Start free: 3 watches