UptimeHunt Docs
Cron Job Monitoring

Monitor systemd Timers

Wire a systemd.timer-triggered service to UptimeHunt using OnFailure= and ExecStartPost=, without editing the job's own script.

Monitor systemd Timers

If your scheduled job already runs as a systemd.timer + .service pair instead of a crontab entry, you can wire it up to a cron monitor entirely in the unit files — no changes to the script itself. The pattern uses two systemd features:

  • ExecStartPost= on the job's own service — ping on success.
  • OnFailure= on the job's own service — activate a small, reusable "reporter" unit when the job's service enters the failed state, which pings /fail.

The job's timer and service

Nothing unusual here — a normal .timer/.service pair, same base name so the timer activates the service by default:

/etc/systemd/system/nightly-backup.timer
[Unit]
Description=Run nightly-backup.service on a schedule

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

[Install]
WantedBy=timers.target

Persistent=true means a run that was missed entirely (the machine was off at 04:00) fires as soon as the system is back up, instead of waiting for the next scheduled time — useful for a laptop or a machine that isn't always on, though on an always-on server it rarely matters.

/etc/systemd/system/nightly-backup.service
[Unit]
Description=Nightly database backup
OnFailure=uptimehunt-ping-fail@%n.service

[Service]
Type=oneshot
ExecStart=/usr/local/bin/nightly-backup.sh
ExecStartPost=/usr/bin/curl -fsS -m 10 --retry 5 -o /dev/null https://ping.uptimehunt.io/<ping_token>
  • ExecStartPost= runs only if ExecStart= succeeded — that's the success ping. If the backup script exits non-zero, ExecStartPost= never runs, and the ping never fires.
  • OnFailure= names one or more units to activate whenever this unit enters the failed state — which is exactly what happens when ExecStart= exits non-zero. %n expands to this unit's own full name (nightly-backup.service), passed as the instance of a templated reporter unit below.

The two paths are mutually exclusive by construction: a run either succeeds (ExecStartPost= pings success) or fails (ExecStart= fails → the unit fails → OnFailure= fires the fail reporter) — never both.

The reusable fail-reporter unit

A small templated unit (note the @.service suffix) that any job's OnFailure= can point at, parameterized by %i — the instance name, which systemd fills in from whatever comes after the @ in the unit that referenced it (here, the failed unit's own name via %n):

/etc/systemd/system/uptimehunt-ping-fail@.service
[Unit]
Description=Report failure of %i to UptimeHunt

[Service]
Type=oneshot
ExecStart=/usr/bin/curl -fsS -m 10 --retry 5 -o /dev/null https://ping.uptimehunt.io/<ping_token>/fail

Reuse it across every job's .service unit by setting OnFailure=uptimehunt-ping-fail@%n.service in each — each job still pings its own monitor's URL from its own ExecStartPost=/reporter pair; the templated unit only saves you from writing a near-identical [Unit]/[Service] block per job.

One monitor per job

Each job needs its own cron monitor (and its own ping URL baked into its own ExecStartPost= / reporter unit) — a shared monitor across unrelated jobs makes both the schedule and the grace period meaningless for either one.

Attaching the failure output

To attach the failed run's journal output as the ping body (so it shows up in the run's log on the monitor's Runs tab), swap the reporter's ExecStart= for a small wrapper that captures journalctl output for the unit that failed:

/etc/systemd/system/uptimehunt-ping-fail@.service
[Unit]
Description=Report failure of %i to UptimeHunt, with a log excerpt

[Service]
Type=oneshot
ExecStart=/bin/sh -c 'journalctl -u %i -n 50 --no-pager | curl -fsS -m 10 --retry 5 -o /dev/null --data-binary @- https://ping.uptimehunt.io/<ping_token>/fail'

Enable it

sudo systemctl daemon-reload
sudo systemctl enable --now nightly-backup.timer

Check the timer is scheduled and see when it last/next fires:

systemctl list-timers nightly-backup.timer

On this page