CronCanary DocsPricingOpen app
← Integration guides

Monitor systemd timer units

systemd timers replaced cron on most modern Linux distros, but unlike cron's MAILTO there is no built-in way to get notified when a timer-triggered service fails or a timer itself is disabled — everything just goes to the journal.

Why systemd timers fail silently

A systemd .timer unit fires an associated .service unit on schedule, and systemctl status will happily show a failed unit right there in plain text — the catch is that nobody is watching systemctl status at 4am. Failures are written to the journal (journalctl -u your.service) and nowhere else unless you wire something up. Several situations make this worse: a timer can be silently left disabled after a package upgrade or a manual systemctl stop during debugging that's never re-enabled; a .service unit can fail immediately on every run for weeks (a bad path, a missing env file, a permissions change) with the journal quietly accumulating identical failures that nobody greps for; and OnCalendar= expressions are easy to get subtly wrong (weekday vs. day-of-month syntax) so the timer fires far less often than intended without ever reporting an error, because from systemd's point of view it did exactly what was scheduled.

Ping from the service unit

The cleanest approach avoids touching your existing script at all: use ExecStartPost= for success and OnFailure= for failure, both at the unit level.

# /etc/systemd/system/nightly-backup.service [Unit] Description=Nightly backup OnFailure=croncanary-fail@%n.service [Service] Type=oneshot ExecStart=/usr/local/bin/backup.sh ExecStartPost=/usr/bin/curl -fsS "$URL"
# /etc/systemd/system/nightly-backup.timer [Unit] Description=Run nightly-backup daily at 04:00 [Timer] OnCalendar=*-*-* 04:00:00 Persistent=true [Install] WantedBy=timers.target

The OnFailure= notifier unit

OnFailure= fires a separate templated unit whenever the main service exits non-zero or times out — it runs even if your script itself never gets a chance to ping, e.g. on an ExecStart that can't even start:

# /etc/systemd/system/[email protected] [Unit] Description=Report failure of %i to CronCanary [Service] Type=oneshot ExecStart=/usr/bin/curl -fsS "$URL/fail"

Report the exact exit code (optional)

If you'd rather capture the real exit status instead of a flat "failed", wrap the script call directly and skip OnFailure=:

ExecStart=/bin/sh -c '/usr/local/bin/backup.sh; rc=$?; curl -fsS "$URL/$rc"; exit $rc'

Set the schedule to match

Give the check a Cron expression equivalent to your OnCalendar= value (*-*-* 04:00:000 4 * * *) with the server's local timezone, and a grace period covering RandomizedDelaySec= if you've set one — systemd deliberately jitters timers under that setting to avoid thundering-herd wakeups, and a tight grace will produce false alerts against that intentional jitter.


Related guides


Add a live status badge to your README

Every check has a public SVG badge that shows its live status (updates within ~1 minute). Paste this into any README — it doubles as a heartbeat anyone on the team can see:

[![CronCanary](https://croncanary.fluxath.app/badge/<your-check-id>.svg)](https://croncanary.fluxath.app)

Copy the exact markdown from your check's detail page. Add ?label=your-text to customize the left label.


Ready to wire this up? Create a free check — 20 checks, all alert channels, no card.