Skip to content
Get started

How to monitor a systemd timer

A systemd timer stops firing when the timer unit is disabled, the machine was off past a non-persistent OnCalendar window, or the paired service unit fails. To monitor it, add an ExecStartPost (or a final ExecStart command) that pings an external monitor on success and alert when the ping doesn't arrive. Use systemctl list-timers to confirm the next and last trigger times.

Why did my systemd timer stop firing?

  • The timer isn't enabled: a timer that was started but not enabled does not survive a reboot. Enable and start it with systemctl enable --now myjob.timer.
  • A missed window without Persistent=true: if the machine was off or asleep during the OnCalendar window, the run is skipped unless Persistent=true is set, which catches up a missed run on next boot.
  • The service unit failed: the timer fired on schedule but the paired .service exited non-zero, so the work didn't complete.
  • A typo in OnCalendar: an unparseable calendar expression means the timer never has a valid next trigger. Validate it with systemd-analyze calendar.

How do I send a heartbeat from a systemd service?

Add an ExecStartPost that pings the monitor. systemd runs ExecStartPost only if the main ExecStart succeeded, so a failing job withholds the ping and triggers the alert:

/etc/systemd/system/nightly.service
[Unit]
Description=Nightly report job

[Service]
Type=oneshot
ExecStart=/usr/local/bin/nightly-report.sh
# Runs only if ExecStart succeeded. A failed job sends no ping.
ExecStartPost=/usr/bin/curl -fsS -m 10 --retry 3 https://ping.cronshield.com/<your-check-id>
/etc/systemd/system/nightly.timer
[Unit]
Description=Run the nightly report at 03:00

[Timer]
OnCalendar=*-*-* 03:00:00
Persistent=true

[Install]
WantedBy=timers.target
Persistent=true catches up a run missed while the machine was off. PING_URL is a placeholder for the endpoint you get when you create a monitor; the CronShield receiver ships in an upcoming release.

How do I confirm the timer is active?

systemctl list-timers --all | grep nightly   # NEXT / LAST trigger times
systemctl status nightly.timer nightly.service
journalctl -u nightly.service --since "1 day ago"   # the job's own logs

list-timers shows whether the timer has a valid next trigger; journalctl shows why the service failed. A missed-ping alert surfaces the outage; on paid tiers, CronShield pulls the service's output so the last log line and a likely cause arrive in the alert instead of you SSHing in to read journalctl.

Add a missed-run alert to this job

The free tier gives you a heartbeat endpoint and an email alert when an expected ping doesn't arrive. Paid tiers add the log-aware diagnosis — the last log line and a likely cause in the alert. The heartbeat receiver ships in an upcoming release; see the plans to learn what each tier adds.

Frequently asked questions

Why didn't my systemd timer run while the server was rebooting?
By default a timer skips any window that elapses while the machine is off. Set Persistent=true in the [Timer] section so systemd runs the job on next boot when a scheduled window was missed.
How do I test a systemd timer without waiting for OnCalendar?
Start the service directly with systemctl start nightly.service to run the job now, confirm the heartbeat ping arrives, then rely on the timer for the schedule.