Monitoring
Monitoring Cron Jobs Without Cronhub: Healthchecks, Heartbeats, and CheckFast's Approach
Cronhub is shutting down in June 2026. Here is what "heartbeat monitoring" actually means, the patterns that survive flaky networks, and how to migrate.
Cronhub announced in late 2025 that they would be sunsetting the service in June 2026. Healthchecks.io continues, BetterStack absorbed Cronitor, and a handful of new entrants (CheckFast included) launched in the past year. The market is reshuffling, and any team running cron monitoring on Cronhub has a hard deadline to migrate.
This post is for the team that has thirty cron jobs scattered across servers, Kubernetes CronJobs, GitHub Actions, and serverless schedulers, and needs to migrate without losing visibility. We will cover the mechanical patterns that make heartbeat monitoring reliable (it is not as simple as "curl the URL after the job"), the differences between the surviving services, and the gotchas in batch jobs, retries, and timezone handling that cause false alerts.
Disclosure: CheckFast offers cron monitoring as one of its products. We launched it specifically because the Cronhub sunset announcement created an obvious migration window. The patterns described here apply to any heartbeat-monitoring service — Healthchecks.io, BetterStack, Cronitor, Dead Man's Snitch, or our own. The goal is to leave you with enough understanding to choose well, not to push a specific vendor.
What "heartbeat monitoring" actually does
A heartbeat monitor is a small server that expects to receive a ping from your job within a configured time window. If the ping arrives, all is well. If the ping does not arrive within the window, the monitor alerts you. That is the entire model — there is no agent on your server, no SDK to instantiate, no API key to rotate. Just a unique URL and an expectation about when it will be hit.
The standard pattern is: at the end of your job, after the work is complete, hit the heartbeat URL. If the job fails before reaching the curl, the heartbeat is not sent, and the monitor alerts. If the job succeeds, the heartbeat is sent, and the monitor stays green.
This sounds trivial but the failure modes are interesting. What if the job succeeds but the curl call fails because of a transient network issue? You get a false alert. What if the job runs three minutes longer than usual due to a slow database — should that alert? What if the job is supposed to run every 5 minutes but the heartbeat URL is rate-limited?
The good monitoring services solve these with grace periods (extra minutes of slack before alerting), retry-friendly endpoints (idempotent pings), and start/end pings to detect both failure-to-start and failure-to-finish. We will work through these.
The ping-after-success pattern (and its limitations)
The simplest pattern is a single ping at the end of the job. In bash, this looks like:
Add && curl -fsS --retry 3 https://hb.example.com/abc123 to the end of the cron line, or run the curl as a separate post-step in your job runner. The && ensures the curl only fires on a successful exit code; the --retry 3 handles transient network failures; the -fsS suppresses progress and fails on HTTP errors so a 500 from the monitor is treated as a script failure.
This pattern catches: process crashes, exit-code-nonzero failures, missing scheduler entirely (the box rebooted and cron is no longer running). It does not catch: jobs that exit 0 but did not actually do the work; jobs that hung mid-execution; jobs that ran successfully but generated invalid output.
Some services support a start-end pattern that automatically detects a run which started but never completed. Healthchecks.io has a /start endpoint and Cronitor uses ?state=run. CheckFast currently records explicit ?type=start and ?type=fail events, while the base URL records success; it does not yet derive hung-run or percentile-duration alerts from those markers.
#!/usr/bin/env bash
# nightly-backup.sh
set -euo pipefail
HEARTBEAT="https://checkfast.io/api/ping/your-checkfast-uuid"
# record a start marker
curl -fsS --retry 3 -m 10 "${HEARTBEAT}?type=start" > /dev/null
# do the actual work
pg_dump production_db | gzip | aws s3 cp - s3://backups/$(date +%F).sql.gz
# record successful completion
curl -fsS --retry 3 -m 10 "$HEARTBEAT" > /dev/nullCheckFast Cron Monitor: new heartbeat URLs, grace windows, and plan-based alert channels.
Set up paid cron monitoringGrace periods and the long-running-job problem
Every cron monitor lets you configure a grace period — extra time beyond the expected schedule before an alert fires. A nightly job scheduled at 02:00 UTC, with a 30-minute grace period, alerts at 02:30 UTC if no ping has arrived. The grace period exists because real jobs do not run in zero time; they take minutes or hours.
The right grace period depends on the variance of your job. A backup that always finishes in 4-5 minutes can use a 10-minute grace. A data pipeline that ranges from 20 to 60 minutes needs at least a 90-minute grace. Setting grace too tight produces false alerts; setting it too loose delays real alerts.
A practical heuristic is grace period = maximum observed runtime over a representative window × 1.5. If your worst-case observed run took 40 minutes, set grace to 60. CheckFast does not currently calculate that percentile or maximum for you, so derive it from application or job logs before configuring the grace window.
Long-running jobs (>1 hour) introduce a complication: the heartbeat URL might receive a single ping per day, with hours of silence in between. Most monitoring services handle this through schedule and grace settings, but verify the exact maximum grace and late-detection behavior before choosing one.
Jitter and retries — the cron-vs-real-world friction
Cron schedules things deterministically. Real systems have jitter — clock drift, scheduler latency, queue delays. A job scheduled at exactly 02:00 every day will sometimes run at 02:00:03 and sometimes at 02:00:47. If your grace period is exactly 30 seconds, you will see false alerts.
The deeper issue is "thundering herd" — every box in your fleet running the same cron at exactly 02:00 hammers shared services. Many systems add deliberate jitter to spread load. Kubernetes CronJobs can use startingDeadlineSeconds and concurrencyPolicy. Systemd timers can use RandomizedDelaySec=. AWS EventBridge has explicit jitter knobs. GitHub Actions schedule precision is poor (±10 minutes is common).
When jitter is intentional, your monitor needs to know the maximum jitter. If GitHub Actions might fire your scheduled workflow up to 10 minutes after the configured time, your grace period must accommodate that on top of the actual job runtime. Failing to account for scheduler jitter is the most common cause of false alerts in monitoring deployments.
Retry handling is similar. If your job uses an outer retry wrapper (for i in 1 2 3; do my-job && break; sleep 60; done, the heartbeat should fire only on final success — not on every iteration. Using && instead of ; enforces this. Retrying inside the job (e.g. tenacity in Python) is fine because the heartbeat is at the end.
Multi-region and multi-instance considerations
If you run the same cron on multiple machines (e.g. a fleet of workers, all running a daily reconciliation), you have a coordination problem. Either pick one canonical instance to send the heartbeat (using leader election, Redis SETNX, or a simple if [[ $(hostname) == "worker-01" ]], or instrument each instance with its own monitor.
Per-instance monitoring is more expensive (more URLs to manage) but more informative — you see which specific instance failed. Per-fleet monitoring with leader election is cheaper but loses per-host visibility. The right choice depends on whether instance failures are independent (worth tracking individually) or correlated (a network outage takes them all down at once).
Multi-region jobs (the same job running on workers in us-east, us-west, and eu-west) almost always benefit from per-region monitoring. Latency differences mean grace periods differ, and a regional outage should not be hidden by another region's success.
For Kubernetes CronJobs, wrap the job command or use a small sidecar that captures the exit code and pings on success. Healthchecks.io publishes integration examples; CheckFast currently documents generic curl-based setup rather than shipping a Kubernetes manifest.
Comparing the alternatives in 2026
Healthchecks.io: the OG, stable, $20/month for the smallest paid tier. Self-hostable. Excellent CLI tools. Best choice for teams that want a no-frills, well-documented, fairly-priced service from a small focused team. Slack and PagerDuty integrations are first-class.
BetterStack (Cronitor inside): BetterStack acquired Cronitor in 2024 and integrated it into their broader monitoring suite. The pricing is per-monitor, slightly higher than Healthchecks.io but with bundled status pages, log aggregation, and incident management. Best choice for teams who want a single vendor for everything.
Dead Man's Snitch: the elder statesman. Still works, still well-priced, but the UI feels stuck in 2018 and integrations are limited compared to Healthchecks.io. Use if you are already using it; skip if you are starting fresh.
CheckFast Cron: our offering. There are no free scheduled monitors. Starter is $9/month for 15 combined cron jobs and monitors with Telegram cron alerts; Pro is $29 for 50 and adds Slack; Agency is $79 for 200. All plans retain the broader one-off diagnostic catalog.
Self-hosting Healthchecks.io: the open-source version is excellent. If you have the operational capacity to run a small Django app with Postgres and a notification fanout, this is the most cost-effective option for >50 monitors. Single-VM deployments are documented in their repo.
Migration plan from Cronhub
Cronhub is shutting down June 30, 2026. Service degradation may begin earlier. Teams should plan to migrate by April 2026 to leave buffer for issues — which means many teams reading this post are at-risk.
Step 1: Export your current Cronhub monitor list. The Cronhub UI does not have a clean export feature — manually list every monitor's name, schedule, grace period, and notification channels. A spreadsheet works.
Step 2: Pick your target service. Decision factors: number of monitors, budget, integration requirements, whether you also need status pages or other monitoring. CheckFast monitoring is paid from Starter; compare it with the current free and paid limits of the other providers before committing.
Step 3: Create the new monitors. Each monitor gets a new URL. Update your cron entries to ping the new URL. We recommend keeping both old and new URLs active for 1-2 weeks to verify the new system catches failures before fully cutting over.
Step 4: Reconnect the notification channels supported by your chosen provider and test each one before cutover. CheckFast cron alerts currently support Telegram on Starter and add Slack on Pro and Agency; choose another provider if email, webhooks, or PagerDuty are requirements.
Step 5: Decommission Cronhub after a quiet week with the new monitors. Export historical alert data first if you need it for SLA reporting.
Side-by-side feature comparison and migration guide if you are choosing CheckFast as your Cronhub replacement.
CheckFast vs Cronhub comparisonEdge cases that bite during real production use
Daylight savings transitions: cron handles DST badly. A job scheduled at 02:30 might run twice in November (when 02:30 happens twice) or not at all in March (when 02:30 is skipped). If your scheduler is set to local time, you will see false alerts on both transitions twice a year. Move all cron schedules to UTC. CheckFast evaluates its five-field cron expressions in UTC, so the expression must match the actual UTC schedule used by your job.
Heartbeat URL caching: if your heartbeat URL is behind a CDN with caching enabled, every ping returns the same cached response and the monitor only sees the first one. Heartbeat services should set Cache-Control: no-store on their endpoints (Healthchecks.io and CheckFast both do); your client should not aggressively cache them. Verify by sending two pings 30 seconds apart and confirming both register on the server side.
Job runs across midnight UTC: a daily job scheduled at 23:55 UTC may finish after 00:00 UTC the next day. Your monitor's "daily" interpretation might assign the late ping to the next day. Check how your service handles cross-day boundary pings — most do it sensibly but it is worth verifying.
Holiday schedules: a job that runs every weekday will not ping on weekends. If you configure it as "every day" with grace, you will alert every Saturday at 02:30. Use cron expressions that match your real schedule, and configure your monitor accordingly. CheckFast accepts cron expressions directly; some services accept only "every X minutes/hours" and require a custom expression for weekday-only.
Maintenance windows: when you intentionally pause a job (e.g. during a deploy or a customer-impacting change), you do not want the monitor to alert. The pattern is to use a service that supports a "paused" or "maintenance" state via API call. Wrap your deploys with a monitor_pause and monitor_resume step.
Frequently asked
Uptime monitoring polls a URL from outside and alerts on failure. Heartbeat monitoring expects an inbound ping from your system and alerts on absence. Heartbeat is right for cron jobs and background tasks (which are not externally pollable); uptime is right for web servers and APIs (which are). Most production setups use both for different things.
Some services accept sub-minute schedules but CheckFast does not: its minimum accepted interval is one minute and cron expressions have five fields. For a job that runs every 30 seconds aggregate health and send at most one CheckFast heartbeat per minute or choose a service with a verified sub-minute contract.
Your job's ping fails (or times out depending on retry config). The monitor records nothing. If the service is down for the full grace period the monitor will alert when it comes back — falsely because your job actually succeeded. This is rare with mature services (their uptime is typically 99.9%+) but is the reason most teams use multiple monitoring services for the most critical jobs.
No. A heartbeat URL contains an unguessable monitor identifier that acts as a bearer secret: anyone who obtains it can forge success start or failure pings and hide a real outage or create false alerts. Keep it in your normal secret manager or protected runtime configuration redact it from logs and rotate the monitor URL if it leaks.
Probably not. Monitor jobs that have business consequences when they fail — backups billing compliance reports customer-facing data sync. Skip jobs that have no consequence (cleaning up old log files regenerating cache that will rebuild lazily). Over-monitoring leads to alert fatigue which is worse than no monitoring.
They are not feature-equivalent. CheckFast currently offers heartbeat URLs grace windows typed start/failure/success events Telegram on Starter and Slack on Pro or Agency. Healthchecks.io has a broader cron-specific integration surface self-hosting and a longer track record. CheckFast's distinction is bundling its simpler cron monitor with SSL DNS email-deliverability SEO and performance tools.
Related reading
Ops & infrastructure
Building a Status Page: When to Buy vs Build vs Use a Bundled Tool
11 min read
Security
SSL Renewal Strategies: Comparing Let's Encrypt, ZeroSSL, and Caddy Auto-Renewal
14 min read
Performance
The Core Web Vitals 2026 Guide: How to Hit Green on LCP, INP, and CLS
12 min read
Email deliverability
DNS Deep Dive: How SPF, DMARC, MX, and DNSSEC Fit Together
12 min read