Healthchecks: Self-Hosted Cron Job and Background Task Monitoring
Healthchecks is a cron job monitoring service. The concept is simple: your scripts "ping" Healthchecks when they run successfully. If a ping doesn't arrive on schedule, you get an alert. It catches the silent failures — the backup script that stopped running three weeks ago, the certificate renewal that quietly errored out.
Uptime monitoring tells you when services go down. Healthchecks tells you when scheduled tasks stop working.
Why Self-Host Healthchecks?
The hosted version at healthchecks.io is excellent, but self-hosting gives you:
- Unlimited checks — No plan limits on how many jobs you monitor
- Data privacy — Cron schedules and ping data stay on your server
- Custom integrations — Modify notification channels and behavior
- No subscription — One-time setup, no monthly fee
The trade-off: you're responsible for keeping the monitoring system itself running. Monitoring your monitor is a real consideration — if your server goes down, Healthchecks goes down with it. For critical monitoring, consider the hosted version or run Healthchecks on a separate server.
Installation
# docker-compose.yml
services:
healthchecks:
image: healthchecks/healthchecks:latest
container_name: healthchecks
ports:
- "8000:8000"
volumes:
- healthchecks_data:/data
environment:
- ALLOWED_HOSTS=*
- DB=sqlite
- DB_NAME=/data/hc.sqlite
- DEBUG=False
- [email protected]
- EMAIL_HOST=smtp.yourdomain.com
- EMAIL_HOST_USER=your-smtp-user
- EMAIL_HOST_PASSWORD=your-smtp-password
- EMAIL_PORT=587
- EMAIL_USE_TLS=True
- PING_BODY_LIMIT=10000
- REGISTRATION_OPEN=False
- SECRET_KEY=your-secret-key-here
- SITE_NAME=My Healthchecks
- SITE_ROOT=https://healthchecks.yourdomain.com
restart: unless-stopped
volumes:
healthchecks_data:
Generate a secret key:
python3 -c "import secrets; print(secrets.token_urlsafe(50))"
docker compose up -d
Create the admin user:
docker exec -it healthchecks ./manage.py createsuperuser
How It Works
The Ping Model
Each check gets a unique ping URL:
https://healthchecks.yourdomain.com/ping/<uuid>
Your cron job hits this URL on success. That's it. If the ping doesn't arrive within the expected schedule + grace period, Healthchecks alerts you.
Adding a Check to a Cron Job
The simplest integration — add a curl to the end of your script:
#!/bin/bash
# backup.sh
borgmatic --verbosity 1 && curl -fsS --retry 3 https://healthchecks.yourdomain.com/ping/<uuid>
The && ensures the ping only fires if the backup succeeds. The -fsS --retry 3 makes curl quiet on success, loud on failure, with retries.
Signal-Based Pings
Healthchecks supports start and failure signals:
#!/bin/bash
PING_URL="https://healthchecks.yourdomain.com/ping/<uuid>"
# Signal start
curl -fsS --retry 3 $PING_URL/start
# Do the work
/usr/bin/my-backup-script
# Signal success or failure
if [ $? -eq 0 ]; then
curl -fsS --retry 3 $PING_URL
else
curl -fsS --retry 3 $PING_URL/fail
fi
The /start signal lets Healthchecks measure job duration and detect jobs that start but never finish.
Sending Logs with Pings
Include output in the ping body for debugging failed jobs:
#!/bin/bash
PING_URL="https://healthchecks.yourdomain.com/ping/<uuid>"
OUTPUT=$(/usr/bin/my-script 2>&1)
curl -fsS --retry 3 --data-raw "$OUTPUT" $PING_URL/$?
The $? exit code maps to /0 (success) or /fail (non-zero). The script output is stored and viewable in the Healthchecks UI.
Configuring Checks
Schedule Types
- Simple — "Expected every X minutes/hours/days" with a grace period
- Cron — Full cron expression (
0 2 * * *for "2 AM daily") with timezone support
Use cron expressions for scheduled tasks with specific timing. Use simple intervals for tasks that run "roughly every N hours."
Grace Period
The grace period is how long Healthchecks waits after a missed ping before alerting. Set this based on how variable your job's timing is:
- Backup at 2 AM that takes 10-30 minutes → Schedule:
0 2 * * *, Grace: 1 hour - Script that runs every 5 minutes → Period: 5 minutes, Grace: 5 minutes
- Weekly report on Sunday → Schedule:
0 9 * * 0, Grace: 2 hours
Tags
Tag your checks for organization:
server:homelab/server:vpstype:backup/type:cert/type:cleanuppriority:critical/priority:low
Tags are filterable in the dashboard and can be used in notification rules.
Alert Integrations
Healthchecks supports many notification channels:
Email (Built-in)
Configured via the environment variables in docker-compose. Each check can have email recipients.
Webhooks
URL: https://your-webhook-endpoint.com/alerts
POST body (JSON):
{
"$NAME": check name,
"$STATUS": "down" or "up",
"$TAGS": space-separated tags
}
Popular Integrations
- Gotify/ntfy — Self-hosted push notifications
- Matrix — Chat notifications
- Discord/Slack — Webhook notifications
- PagerDuty — Incident management
- Telegram — Bot notifications
- Apprise — Meta-notification service supporting 90+ channels
What to Monitor
Essential Checks for Self-Hosters
| Check | Schedule | Grace | Priority |
|---|---|---|---|
| Backup script | 0 2 * * * |
2h | Critical |
| Certificate renewal | 0 3 1 * * |
24h | High |
| Docker image updates | 0 6 * * 0 |
12h | Medium |
| Disk space check | */30 * * * * |
1h | High |
| Database vacuum | 0 4 * * 0 |
6h | Low |
| DNS record check | 0 */6 * * * |
12h | Medium |
| SMART disk check | 0 5 * * * |
24h | High |
Monitoring Docker Containers
Use Docker healthchecks with a reporting script:
#!/bin/bash
# check-containers.sh
UNHEALTHY=$(docker ps --filter health=unhealthy --format '{{.Names}}' | tr '\n' ', ')
if [ -z "$UNHEALTHY" ]; then
curl -fsS https://healthchecks.yourdomain.com/ping/<uuid>
else
curl -fsS --data-raw "Unhealthy: $UNHEALTHY" https://healthchecks.yourdomain.com/ping/<uuid>/fail
fi
Monitoring with Systemd Timers
For systemd-based systems, add an ExecStartPost or OnSuccess handler:
# /etc/systemd/system/backup.service
[Service]
ExecStart=/usr/local/bin/backup.sh
ExecStartPost=/usr/bin/curl -fsS https://healthchecks.yourdomain.com/ping/<uuid>
Healthchecks vs Uptime Kuma vs Gatus
| Feature | Healthchecks | Uptime Kuma | Gatus |
|---|---|---|---|
| Focus | Cron/task monitoring | Service uptime | Service uptime |
| Check model | Ping-based (passive) | Probe-based (active) | Probe-based (active) |
| Cron monitoring | Excellent | Basic | No |
| Job duration tracking | Yes | No | No |
| Log capture | Yes (ping body) | No | No |
| Status page | Basic | Beautiful | Yes |
| Setup complexity | Low | Very low | Low |
| Configuration | Web UI | Web UI | YAML |
These tools complement each other:
- Healthchecks for cron jobs and scheduled tasks
- Uptime Kuma for service availability and status pages
- Both together for comprehensive monitoring
Verdict
Every self-hoster has experienced the slow realization that a backup script hasn't run in weeks, or that a certificate renewal failed silently. Healthchecks prevents this class of failure with minimal effort — adding a single curl to your scripts.
The self-hosted version is lean, reliable, and easy to maintain. The only real consideration is the meta-monitoring problem: if you run Healthchecks on the same server as your cron jobs, a server failure takes both down. For critical infrastructure, consider running Healthchecks on a separate machine or using the hosted version alongside your self-hosted instance.