← All guides

healthchecks.io alternative β€” cron job monitoring with one curl, no account

The scariest failure mode a scheduled job has is silence. The nightly backup didn't crash loudly β€” the server it ran on got rebuilt, the crontab entry didn't come along, and nothing anywhere is even trying to run it. An uptime monitor can't catch this: your site is up, your API is up; it's the job that's dead, and jobs don't have URLs to check.

The fix is a dead man's switch (a "heartbeat monitor"): instead of a monitor calling you, your job pings a URL every time it runs. If the pings stop arriving, the alarm fires. It's the model healthchecks.io built a whole product on, and it works for anything on a schedule β€” cron jobs, systemd timers, GitHub Actions crons, scheduled agent tasks, backup scripts, data pipelines.

1. First, honestly: healthchecks.io is excellent

If you want a cron-monitoring dashboard, use healthchecks.io. Their free "Hobbyist" plan gives you 20 checks with 100 log entries each (verified on their pricing page, September 2026) β€” a genuinely generous free tier, not a teaser. The project is open source and self-hostable, they give the $20/mo Business plan free to open-source projects and nonprofits, alerting covers email and dozens of integrations, schedules can be full cron expressions with time zones, and detection granularity goes down to a minute. It is the deserved default in this category.

What this page is for is the other case: you have one script and you want the switch armed right now, from the terminal you're already in. healthchecks.io needs a signup first (email + magic link) before you have a ping URL. That's thirty seconds for a human with a mail client β€” and a wall for a shell script, a CI step, or an agent that wants to arm a watchdog for itself.

2. The one curl

curl -X POST https://mockbird.mockbird.workers.dev/api/status/heartbeat \
  -H 'content-type: application/json' \
  -d '{"name":"nightly backup","period_minutes":1440,"notify":"https://hooks.slack.com/services/T000/B000/XXXX"}'

period_minutes is how often the job is supposed to run; notify is a Slack incoming webhook, a Discord webhook, or any HTTPS endpoint you control. The response comes back immediately with everything you need:

{
  "id": "hb-…",
  "secret": "…",
  "ping_url": "https://mockbird.mockbird.workers.dev/ping/p-…",
  "ping": "curl -fsS -m 10 https://mockbird.mockbird.workers.dev/ping/p-…",
  "period_minutes": 1440,
  "grace_minutes": 720,
  "alert_when": "no ping for longer than 24h + 12h grace (evaluated every 30 min)",
  "status_page": "https://mockbird.mockbird.workers.dev/status/hb-…",
  "badge_markdown": "[![nightly backup status](…/badge.svg)](…)",
  "feed_atom": "https://mockbird.mockbird.workers.dev/status/hb-…/feed.xml"
}

No account, no email, no dashboard. A confirmation delivery hits your webhook right away so you know the channel works, and creation counts as the first ping β€” the clock is already running.

No webhook handy? Omit notify and you get a pollable heartbeat instead: the create response includes a poll URL that returns any missed-check-in / checked-in-again transitions since your last poll plus the current ping age — no endpoint to host. How poll mode works.

3. Wire it into the job

Append the ping to the end of the job with &&, so it only fires when the job succeeded:

# crontab: run backup at 03:00, check in only on success
0 3 * * * /usr/local/bin/backup.sh && curl -fsS -m 10 https://mockbird.mockbird.workers.dev/ping/p-XXXX

That one line covers all three failure modes: the job crashed (no && ping), the job never started (no ping), the whole machine is gone (definitely no ping). -fsS -m 10 keeps the ping quiet, failure-visible, and bounded. The same line works in a systemd timer's ExecStartPost, at the end of a GitHub Actions scheduled workflow, or as the last step of an agent's scheduled task.

4. What fires, and when

We evaluate every 30 minutes. If no ping has arrived within period_minutes + grace (grace defaults to half the period, clamped 5m–24h; override with grace_minutes), your webhook gets one service.down event β€” "missed its check-in" β€” and when pings resume, one service.up. No repeat nagging, no flood. Delivery formats are sniffed from the URL: hooks.slack.com gets {"text": …}, Discord webhooks get {"content": …}, and anything else gets a JSON payload signed with your secret (x-mockbird-signature: sha256=hex(hmac-sha256(secret, body))) β€” the signature-verification snippet in the uptime guide applies verbatim.

5. The status page, badge, and feed

Every heartbeat also gets a public, unguessable-URL status page (/status/hb-…), a live README badge (/status/hb-…/badge.svg), and an Atom feed of missed/resumed check-in events (/status/hb-…/feed.xml) that any feed reader can watch. One deliberate design choice worth knowing: the ping URL is a separate secret from the page URL. You can embed the badge in a public README and nobody who sees it can fake a check-in β€” the page and badge show only the name and state, never the ping URL. Management (info, delete) requires the secret from the create response.

6. Honest comparison

Facts checked September 2026 against each vendor's own pricing/FAQ pages β€” verify current numbers yourself:

Free tierPick it when
healthchecks.io20 checks, 100 log entries each, email + many integrations, cron-expression schedules, ~1-min granularity; open source, self-hostable; Business plan free for OSS/nonprofitsYou're monitoring a fleet of cron jobs and want a dashboard, log history, and email β€” the deserved default.
Cronitor5 monitors free; usage-based paid ($2/mo per monitor)You want cron monitoring plus performance metrics and status pages in one commercial tool (our Cronitor page).
Dead Man's Snitch1 free snitch, account required; integrations from $19/moYou have exactly one job and like their smart-alert tooling (our page on it).
Mockbird heartbeat5 live heartbeats/IP (deleting frees the slot), 100 total, periods 30 min–7 days, webhook alerts + public status page/badge/feedYou want the switch armed this minute with one curl, no account β€” or a script/CI job/agent is arming it for itself.

Where the incumbents win, plainly: minute-level detection (our evaluation runs every 30 minutes, so period_minutes starts at 30 β€” this is for jobs on human schedules, not sub-minute liveness), email/SMS/push channels, cron-expression schedules with time zones, log history you can browse, teams, and years of production trust. We do: one curl, one webhook on miss, one on recovery, a status page + badge + feed, zero setup friction, no account anywhere in the loop.

7. Managing it

# info + ping URL + recent evaluations
curl "https://mockbird.mockbird.workers.dev/api/status/heartbeat/hb-XXXX?secret=YOUR_SECRET"

# disarm
curl -X DELETE "https://mockbird.mockbird.workers.dev/api/status/heartbeat/hb-XXXX?secret=YOUR_SECRET"

The endpoint is self-documenting β€” GET /api/status/heartbeat returns full usage as JSON. If the URL you want to watch is a website rather than a job, the same machinery does classic URL monitoring: see the uptime-monitoring guide.

Limits while free: period_minutes 30–10080, grace defaults to period/2 (5m–24h), 5 live heartbeats per IP (deleting one frees the slot immediately), 100 total (small free capacity β€” if you hit the cap, try later), evaluation every 30 minutes, best-effort webhook delivery, 5 consecutive failed deliveries auto-remove the heartbeat.
⚑ Mockbird's day job is instant mock REST/GraphQL APIs: this link creates a live, seeded e-commerce backend (products, orders, customers, reviews) in one click β€” real URL, full CRUD, no signup. Or import an OpenAPI spec, db.json, CSV, Postman collection, or HAR and mock your exact shapes.