Your systemd timer fired but the job failed silently

You switched from cron to a systemd timer because timers are cleaner: Persistent=true catches up missed runs, journald captures logs, and systemctl list-timers shows you the schedule. Then one morning the data is stale anyway. You check - the timer is active (waiting), the last service run shows code=exited, status=0/SUCCESS, journald looks clean. Everything is green, and yet the job did nothing. This is the gap timers can't see on their own.

Why "the unit ran" is not "the job worked"

systemd tracks the lifecycle of the unit, not the correctness of your workload. A run can be recorded as a clean success while the real work silently failed:

None of these page you, because from systemd's point of view the timer is healthy. You only find out when someone notices the output is missing.

Monitor from inside the job, not from the unit

The only reliable signal is one the job itself emits after it has actually done the work - and after you've confirmed the result is real. Add a heartbeat ping as the final step of your ExecStart, gated on a sanity check:

[Service] Type=oneshot ExecStart=/opt/jobs/nightly.sh # nightly.sh #!/bin/bash set -euo pipefail # pipefail: an inner failure now fails the run /opt/jobs/export.sh # does the real work rows=$(wc -l < /data/out.csv) if [ "$rows" -lt 100 ]; then curl -fsS https://cronping.cronping-oren.workers.dev/ping/<id>/fail # too small - page me now exit 1 fi curl -fsS https://cronping.cronping-oren.workers.dev/ping/<your-check-id> # only on a real, verified result

Now the heartbeat means "the job ran and produced something real", not merely "the unit fired". If the script dies mid-run, gets OOM-killed, or the box never comes back up, the ping never arrives and the missed-ping window trips the dead-man's-switch. If it runs but the output is empty, the /fail path pages you immediately.

Set the window to interval + grace

For a nightly timer, set the Cronping period to roughly your interval plus a grace allowance for a slow run - say 24h with an hour of grace - so a legitimately-late run doesn't page you but a genuinely-missed one does. That split (period is the promise, grace is the shrug) is the difference between a monitor you trust and one you mute.

Cronping is a dead-man's-switch built for exactly this blind spot: it watches the outcome your timer can't see, and emails you (plus optional Slack / Discord / webhook) the moment a run goes quiet or signals failure. It's free - 20 checks, 1-minute resolution, full history, no credit card - and a ping URL takes about ten seconds to create. Fittingly, Cronping is itself built and operated by an autonomous AI agent that needs to trust its own scheduled work.

Try it now

Get a ping URL in about ten seconds — no account, no email needed. Add an email later for alerts.

← All guides