Back to blog

Guides · Dec 8, 2025

Pair Crontap with Healthchecks.io for end-to-end monitoring

Crontap alerts on failed runs. It cannot alert on the run that never happened: a paused schedule, a deleted schedule, an upstream outage. Healthchecks.io is the dead-man piece. Pair the two and you cover both kinds of failure for free or close to it.
crontap.com / blog
Crontap alerts when a job fails. It cannot alert when the job never runs at all (the schedule got paused, deleted, or hit an outage). Healthchecks.io is the dead-man piece: it pages you on absence. Pair the two and you cover both failure modes for around $3.25/mo across both free tiers.

You set up a weekly sales report on Crontap. Every Monday at 5am Europe/London, the schedule fires https://yoursite.com/wp-cron.php, WordPress queues your reporting plugin, the email goes out, and your manager opens it before they finish their first coffee. This works, until one Monday, the email does not arrive. You log into Crontap and there is no run on Monday at all. No green, no red, no entry. The schedule did not fire. Crontap's email-on-failure alert did not trigger because there was no failed run; there was no run, period. That is the gap a dead-man check fills. This post is about pairing Crontap with Healthchecks.io for the kind of failure that "the alert when nothing fails" cannot catch.

If you want the short version: keep your Crontap schedule. Add a single curl call at the end of your job that pings a Healthchecks.io URL on success. Configure the Healthchecks.io check to expect a ping inside a tolerance window. If no ping arrives, Healthchecks pages you. The two together cover both kinds of failure: the run that failed, and the run that never happened.

What dead-man monitoring is and why it matters

A scheduled job has two failure modes. The loud one: the job runs, hits an exception or a 500, and tells you about it. Crontap alerts on this through its 4xx/5xx detection. The silent one: the job does not run at all. There is no error to surface because there is no execution. The downstream consumer (your inbox, your dashboard, your customer) just waits.

A dead-man check is the alert you want for the silent case. Instead of monitoring "did this run fail", it monitors "is this thing still alive". You give the monitor a heartbeat URL and a tolerance window. The job pings the URL each time it runs. If the monitor does not see a ping inside the window, it pages you. The metaphor is the dead-man's switch on a train: the driver squeezes the lever every minute; if the squeeze stops, the train brakes.

For a weekly report at 5am Monday, the check expects a ping every 7 days with a 30-minute grace, the job pings on success at the end of each run, and if the next ping does not arrive by 5:30am the following Monday, you get an alert. Crontap monitors the job. Healthchecks.io monitors the schedule.

Why Crontap's built-in failure alerts are not enough on their own

Crontap's built-in alerts fire when the HTTP request returns 4xx or 5xx, when it times out, or when the retry budget is exhausted. What they do not fire on, by design: the schedule was paused (you paused it three weeks ago to debug and forgot to unpause), the schedule was deleted (a teammate cleaned up "old" schedules and your weekly was named ambiguously), Crontap itself had a regional outage at the moment your schedule was due, or the account got suspended (the credit card on the Crontap account expired).

In every one of those cases, there is no failed run to alert on. The fire just never happens. Crontap's alerting layer cannot raise an alert about an absence; absence is what dead-man checks specialize in. This is not a Crontap-specific gap; every cron platform has it, including system cron, Vercel Cron, GitHub Actions cron, and any in-process scheduler.

The two-schedule pattern

The setup is two halves. The first half is your normal Crontap schedule. The second half is a Healthchecks.io check that listens for a heartbeat from the job's success path.

Crontap (Mon 05:00 Europe/London)  →  HTTPS  →  your job runs  →  curl https://hc-ping.com/<uuid>
                                                                         │
                                                                         ▼
                                                          Healthchecks.io (expects a ping every 7 days)
                                                                         │
                                                                         ▼
                                                  If no ping in 7 days + 30min: page on-call

The contract:

  • Crontap holds the clock.
  • Your job holds the work and pings on success.
  • Healthchecks.io holds the absence detector.

If the job runs and pings, both halves are happy. If the job runs and fails, Crontap alerts. If the job does not run at all, Healthchecks alerts. Three failure modes, three alerts.

Setup: create the Healthchecks.io check

Healthchecks.io is the canonical hosted dead-man service. It has a free tier (20 checks at the time of writing; see the pricing page). The setup is a few minutes.

  1. Sign up at healthchecks.io (free tier, no credit card).
  2. Click Add Check. Give it a name that matches the schedule ("weekly-sales-report").
  3. Set the Schedule type. Two options:
    • Simple. Period 7 days, grace 30 minutes. Use this for the weekly report case.
    • Cron. Paste the cron expression 0 5 * * 1, set the timezone to Europe/London, set the grace window. Use this if your cadence is irregular (every weekday but not weekends, last day of the month, and so on).
  4. Save the check. Healthchecks generates a ping URL like https://hc-ping.com/<uuid>. Copy it.

That is the dead-man side done. The check now expects a ping at the cadence you specified, and will page you (via email, Slack, Discord, Telegram, PagerDuty, OpsGenie, webhooks, or the other integrations Healthchecks supports) if no ping arrives in the window.

For the worked example below, Healthchecks is configured to expect a ping by 5:30am Monday in Europe/London. If 5:30am rolls around with no ping, the on-call rotation gets a Slack alert and an email.

Add the ping to the end of your job

The ping is a one-line curl at the end of the success path. The placement matters: the ping should fire only if the job actually completed the work.

For the WordPress weekly sales report case, drop it in the plugin or the cron handler:

function my_weekly_sales_report() {
  $report = generate_sales_report();
  if (! send_report_email($report)) {
    error_log('Failed to send sales report');
    return; // do not ping on failure
  }
  wp_remote_get('https://hc-ping.com/<uuid>', ['blocking' => false]);
}

For a bash cron handler the equivalent is generate-and-send-report.sh && curl -fsS https://hc-ping.com/<uuid>. The && matters: if the report script fails, the ping is not sent, Healthchecks does not see a heartbeat, and the alert fires. Healthchecks also supports /start and /fail endpoints for richer reporting; Crontap's built-in alerts cover the failure case, so you usually do not need them. See the Healthchecks docs for details.

Setup: create the Crontap schedule

Head to Crontap and create the schedule the same way you would for any other job.

  1. URL. The work URL: for the WordPress case, https://yoursite.com/wp-cron.php?doing_wp_cron=1; for a Node backend, https://api.yourapp.com/jobs/weekly-report.
  2. Method. GET for wp-cron, POST for a custom backend route.
  3. Headers. If your endpoint has a bearer (it should, for non-WordPress), set Authorization: Bearer <secret>.
  4. Cadence. "Every Monday at 5:00am" or 0 5 * * 1. Crontap previews the next 5 fires inline.
  5. Timezone. Europe/London. Crontap stores timezone per schedule and handles DST automatically.
  6. Failure alerts. Add an integration: email / webhook (Slack / Discord / Telegram). Crontap fires on 4xx and 5xx, so a failed wp-cron tick or a failed report endpoint lands in your channel within seconds.

Press Perform test to confirm the URL works and the response is what you expect. The first real fire is whichever Monday at 5:00am rolls around next.

Fix this in 60 seconds with Crontap. Free tier available. No credit card. Schedule your first job →

That is both halves wired up. From here, every Monday at 5am: Crontap fires, the job runs, the job pings Healthchecks, both monitors stay green. If anything breaks the chain, exactly one monitor lights up depending on where the break was.

Worked example: a UK restaurant on WordPress sending a weekly sales report

Take a UK restaurant on WordPress sending a weekly sales report at 5am Monday in Europe/London. WordPress runs a custom reporting plugin that queries the WooCommerce orders table for the previous week, formats a summary, and emails it to the manager. wp-cron is disabled (the WordPress Plugin Handbook explains why; the sibling post replace WordPress wp-cron with a real scheduler walks through the full setup). Crontap fires https://restaurant.example.co.uk/wp-cron.php?doing_wp_cron=1 every Monday at 5:00am Europe/London. The reporting plugin pings https://hc-ping.com/<uuid> at the end of its success path. Healthchecks expects a ping every 7 days with a 30-minute grace.

Four scenarios:

  1. Happy Monday. Crontap fires at 05:00:00. WordPress runs the plugin. The report is emailed. The plugin pings Healthchecks. The manager reads it at 06:30 with their espresso. Both monitors stay green.
  2. The report endpoint returned 500. The plugin threw an exception (the WooCommerce table had a stuck migration). Crontap sees the 5xx, retries once, alerts on final failure. The SRE fixes the migration, next week is green. Healthchecks does not alert (absence is shorter than the 7-day window).
  3. The Crontap schedule was paused. A teammate "tidied up" last Tuesday and paused the weekly report. By 5:30am Monday, Healthchecks has not seen its ping; the alert lands in the on-call channel. The team finds the paused schedule, unpauses it, and runs the report manually.
  4. Crontap outage at exactly 05:00 Monday. Rare, but possible. By 5:30am, Healthchecks has not seen a ping; the alert lands. The team waits 15 minutes, sees Crontap come back, fires the schedule manually for that missed Monday, the report goes out at 5:55am.

In scenarios 3 and 4, the Crontap-only setup would have left the team in the dark until the manager noticed the missing email mid-Monday. The dead-man check moves discovery from "manager noticed" to "automated alert at 5:30am".

Why this is a different failure mode from Crontap's failure alerts

There is a tendency to look at this and ask "is this not redundant?" It is not. The two alert types catch genuinely different things:

| Failure mode | What is broken | Who alerts | |---|---|---| | Job ran, returned 5xx | The work logic | Crontap (4xx/5xx alert) | | Job ran, returned 4xx | The auth, the URL, the request shape | Crontap (4xx/5xx alert) | | Job timed out | The downstream is slow or stuck | Crontap (timeout alert) | | Job did not run (paused) | The schedule itself | Healthchecks (dead-man) | | Job did not run (deleted) | The schedule itself | Healthchecks (dead-man) | | Job did not run (Crontap outage) | The scheduler service | Healthchecks (dead-man) | | Job did not run (account suspended) | The scheduler account | Healthchecks (dead-man) |

The split is clean. Crontap's domain is "the run failed". Healthchecks's domain is "no run happened".

For workloads where the schedule itself is critical (customer-facing reports, regulatory exports, end-of-day rollups), this split is what teams reach for. For low-stakes background work where a missed week is fine, Crontap's alerts alone are usually plenty.

Pricing: how much does the pair cost

Both halves have free tiers, and for most small-to-medium teams the combination stays free or close to free.

  • Crontap. Free tier available. Pro is $3.25/mo flat (annual) for 1-minute cadence and the higher schedule limit.
  • Healthchecks.io. Free tier covers 20 checks (see the pricing page). Paid tiers add more checks and team features.

For the weekly-sales-report case, both halves are free. Even a team running 20 critical schedules can usually fit them all into Crontap Pro plus the Healthchecks free tier and pay roughly $3.25/mo total for the on-time guarantee plus the dead-man coverage.

When to skip the dead-man check

Not every Crontap schedule warrants a paired Healthchecks check. Skip it for hobby projects where a missed run is invisible, for background work that catches up on the next fire (cache warmers, idempotent imports, cleanup sweeps), and for high-cadence schedules where any individual missed fire is buried in noise.

Pair them for customer-facing schedules (reports, notifications, exports), compliance or finance schedules (end-of-day rollups, end-of-month invoicing), daily or weekly schedules where the cost of a missed fire is a 24-hour delay before anyone notices, and regulated environments where audit logs of intended cadence are valuable on their own. Rule of thumb: if the job missing entirely would matter, pair it.

FAQ

Can I use Healthchecks alone instead of Crontap?

Healthchecks is a monitor, not a scheduler. It listens for pings; it does not fire requests. You need something to actually run the job (Crontap for HTTP cron with retries and IANA timezones, or system cron, or any other scheduler). Healthchecks complements; it does not replace.

Can I use Crontap's failure alerts alone, no Healthchecks?

For most schedules, yes. The dead-man pair is specifically for the small set of schedules where "the run did not happen" is a failure mode you cannot afford to miss.

What grace window should I set?

Slightly larger than the cadence period plus a comfortable margin. For a weekly report at 5am Monday, a 30-minute grace is standard. For an hourly cache rebuild, 5-10 minutes. For a per-minute health probe, 1-2 minutes. Too tight gives false positives; too loose makes the alert late.

Does the ping count against my Healthchecks rate limits?

The ping is a single GET to hc-ping.com. Healthchecks does not throttle pings on the free tier within reasonable cadence; the Healthchecks docs describe the per-second limits.

Where does the Healthchecks ping URL go for a WordPress job?

The UUID in the URL is the auth, so treat the ping URL like a bearer (do not commit it to a public repo, do not log it). For the WordPress case, store it as a constant in the plugin or in wp-config.php.

Does Healthchecks support timezones in the cron mode?

Yes. The "Cron" schedule type takes a cron expression and a timezone. Set them to match the Crontap schedule (0 5 * * 1 and Europe/London for the weekly report case) so both halves agree on the calendar.

References

Related on Crontap

From the blog

Read the blog

Guides, patterns and product updates.

Tutorials on scheduling API calls, webhooks and automations, plus deep dives into cron syntax, timezones and reliability.

Alternatives

Vercel Cron every minute: beating the Hobby hourly limit

Vercel Cron caps Hobby at hourly cadence and 5 jobs, and ties every change to a redeploy. Here is the external cron pattern teams use to ship per-minute schedules, per-IANA timezones, and one dashboard across projects without paying $20/mo per user for Pro.

Alternatives

Cloud Run cron without Cloud Scheduler

Cloud Scheduler costs $0.10 per job per month after the first 3 and asks for OIDC plus IAM bindings on every target. Here is the IAM-free pattern Cloud Run teams use to fire their .run.app URLs on a clock with one bearer token and one dashboard across every GCP project.

Alternatives

Heroku Scheduler alternative: any cron expression without the add-on

Heroku Scheduler caps you at three cadences and account-wide UTC, and spins a one-off dyno per run. Here is the external cron pattern that gives you any cron expression, per-schedule timezones, and zero per-execution dyno spin-up cost.

Guides

Running an OpenAI sentiment pipeline on a real scheduler

OpenAI batch work needs a clock, not a user session. Here is the scheduled HTTP-route pattern teams use to drain LLM batches at a sustainable rate inside OpenAI's rate limits, with per-task failure alerts.

Reference

Cron syntax cheat sheet with real-world examples

Cron syntax without the math. Every pattern you're likely to reach for (every 5 minutes, weekdays, business hours, first of the month), with a practical example and a link to a free debugger.