How to Know When a Cron Job Didn't Run (Self-Hosted Healthchecks)

Know when a cron job didn't run: Healthchecks: a dead-man's switch for every scheduled job.

The backup script stops running — the drive unmounted, cron got confused after a time change, the script errored on line 3 — and you find out months later. Uptime Kuma (tutorial 35) can't see it; nothing is "down". A dead-man's-switch monitor can: each job pings a URL when it finishes, and if the ping doesn't arrive on time, you get a message. Healthchecks is the self-hosted version of healthchecks.io, and the ping is one curl.

Quick reference

TaskWhat
Folder~/docker/healthchecks/compose.yaml
Webhttp://server-ip:8000
Add a checkAdd Check → name, period, grace → copy the ping URL
Ping at end of a jobcurl -fsS -m 10 --retry 3 https://hc.example.com/ping/UUID
Report a failure.../ping/UUID/fail
AlertsIntegrations → ntfy, Telegram, email, Discord, Slack

1. The compose file

mkdir -p ~/docker/healthchecks && cd ~/docker/healthchecks
nano compose.yaml

services:
healthchecks:
image: lscr.io/linuxserver/healthchecks:latest
container_name: healthchecks
ports:
- "8000:8000"
environment:
PUID: 1000
PGID: 1000
TZ: Europe/London
SITE_ROOT: https://hc.example.com
SITE_NAME: Home Checks
SUPERUSER_EMAIL: john@example.com
SUPERUSER_PASSWORD: ${ADMIN_PASSWORD}
SECRET_KEY: ${SECRET_KEY}
REGISTRATION_OPEN: "False"
APPRISE_ENABLED: "True"
volumes:
- ./config:/config
restart: unless-stopped

.env with a long SECRET_KEY and ADMIN_PASSWORD. SITE_ROOT is the address the ping URLs will use — the LAN address is fine if jobs only run at home.

docker compose up -d
sudo ufw allow from 192.168.1.0/24 to any port 8000

2. Add a check

Log in at http://192.168.1.50:8000. Add Check:

Copy the ping URL: http://192.168.1.50:8000/ping/3a9b.... For jobs on a cron schedule, choose Cron instead of simple and paste the schedule — Healthchecks then knows exactly when to expect each ping.

3. Ping from the job

At the end of the backup script (tutorial 23 / 55):

curl -fsS -m 10 --retry 3 -o /dev/null https://hc.example.com/ping/3a9b...

Or wrap the whole job so success and failure are both reported, with the output attached:

#!/bin/bash
URL=https://hc.example.com/ping/3a9b...
curl -fsS -m 10 --retry 3 -o /dev/null $URL/start
if /usr/local/bin/backup.sh > /tmp/backup.out 2>&1; then
curl -fsS -m 10 --retry 3 --data-binary @/tmp/backup.out $URL
else
curl -fsS -m 10 --retry 3 --data-binary @/tmp/backup.out $URL/fail
fi

/start lets it measure duration; /fail alerts immediately rather than waiting for the grace period; the body shows up in the check's log so you read the error without SSH.

The one-line cron form:

0 2 * * * /usr/local/bin/backup.sh && curl -fsS -m 10 --retry 3 -o /dev/null https://hc.example.com/ping/3a9b...

4. What to watch

Anything that should happen regularly:

5. Alerts

Integrations → Add: ntfy (tutorial 93) with your topic and token, Telegram, email via SMTP, or Apprise for anything else. Each check can use different integrations; the default is all of them. You'll get "Nightly backup is DOWN" when a ping is late, and "is now UP" when it returns.

Dashboard

Status badges per check and a read-only status page (/status/) make a nice Homepage widget (tutorial 94 supports Healthchecks directly) — a row of green dots for every job on the server.

Troubleshooting

Ping URL shows localhost. SITE_ROOT is wrong; set it to the address jobs will reach.

Check always "late" by an hour. Time zone mismatch between the server, the container's TZ and the cron schedule you pasted.

curl fails with certificate errors from a Pi. Through the https proxy the Pi needs up-to-date CA certs (sudo apt install ca-certificates), or use the LAN http address.

Too many alerts after a reboot. Grace time too short for jobs that run on boot; or mark checks Paused during maintenance.

Wrapping up

One container, a check per job, a curl at the end of each script. The silent failures — the backup that quietly stopped in March — become a message within hours, and the status page shows every scheduled job on the server as a green dot you can glance at.

Call us