← Blog

How to Monitor Cron Jobs With Heartbeat Checks

· 6 min read

Your site is up. Your API responds in 180 ms. And your nightly backup has not run in eleven days.

That is the problem with background jobs. They fail quietly. A cron job, a scheduled task, a queue worker, a billing run - none of them return an error page you can see. When they stop, nothing changes on the surface. Uptime monitoring stays green because the website never went down. The job just stopped doing its work, and nobody finds out until a client asks why last month's report is empty.

Cron job monitoring closes that gap. And the simplest, most reliable way to do it is a heartbeat check.

Why background jobs fail silently

Uptime checks ask a question your users can see: is the page loading?

Most failures inside a cron job never touch that question. A job can fail because:

  • An API token expired and the sync returned a 401 that nobody read.
  • Two runs overlapped and a lock file blocked the second one.
  • The server rebooted and the scheduler never came back.
  • A deploy changed a path, so the script ran and exited in under a second.
  • The database was reachable when the job started but not when it wrote.

In every case, the job produced no visible symptom. The error lives in a log file nobody opens, or it never got logged at all. Your site looks fine while the work quietly stops.

What a heartbeat check actually is

A heartbeat check flips the question around.

Instead of asking your server "is this job working?", the job itself reports in every time it finishes. You get a private URL. At the end of each run, your job sends one request to that URL. That request is the heartbeat.

That is the entire idea. No agent to install, no library to learn, no changes to how the job does its work. If the heartbeat arrives on schedule, the job is healthy. If it does not arrive within the window you expect, something went wrong - and that is when you get alerted.

The logic is worth stating plainly: a missing heartbeat is the signal. You are not monitoring output. You are monitoring whether the job showed up at all.

Step 1: List every task that matters

Open your crontab, your scheduler, your worker queue. Write down every recurring task and answer two questions for each:

  1. What breaks if this never runs again?
  2. How long would it take someone to notice?

Anything with a real consequence and a slow discovery time belongs on the list. The usual suspects:

  • Nightly database and file backups
  • Data syncs between tools (CRM to billing, store to warehouse)
  • Invoice and billing runs
  • Report generation and scheduled emails
  • Cache warming and index rebuilds
  • Queue workers that process signups, webhooks, or file uploads
  • SSL certificate and domain renewal jobs

You do not need to cover all of them on day one. Start with the ones that would embarrass you or a client if they stalled for a week.

Step 2: Send a heartbeat at the end of each run

Add a single request to the end of each job, after the work succeeds. In most languages it is one line - a curl call, a fetch, or a small HTTP request.

0 3 * * * /usr/local/bin/backup.sh && curl -fsS https://your-monitor/heartbeat/backup-key

Two details matter here.

First, the heartbeat must fire after the job completes, not when it starts. A job that starts and then crashes should not report healthy.

Second, chain it with && so the heartbeat only sends on success. If your script exits with an error, no heartbeat is sent, and the monitor treats the missing ping exactly like a failed job.

If you cannot modify the job, wrap it in a tiny script that runs the job, checks the exit code, and pings the URL on success. The wrapper is usually five lines.

Step 3: Set the expected window

A heartbeat check needs to know when to worry. You give it a schedule and a grace period.

For a job that runs every 24 hours at 03:00, a sensible window is "expect a ping every 24 hours, alert if 30 minutes pass with nothing." The grace period absorbs normal variation - a backup that sometimes takes 12 minutes instead of 8 should not page you.

A few rules of thumb:

  • Cron jobs: expected interval plus a grace period of 10 to 30 percent of that interval.
  • Frequent workers: if a worker runs every minute, alert after 5 to 10 missed minutes, not one.
  • Long jobs: match the window to real run times, then add buffer. Alerting at 60 minutes for a job that occasionally takes 55 is noise.

The goal is an alert you trust. If it fires on normal behavior, you will start ignoring it, and an ignored alert is the same as no alert.

Step 4: Confirm the failure before you wake someone

Here is where most simple monitors fall short. A single missed ping can mean anything: the job broke, or the network hiccupped, or the monitor itself had a bad minute.

The fix is to verify from more than one place. When a heartbeat is missed, check it from a second and third region before declaring a failure. A job that is genuinely down will look down everywhere. A transient network blip usually shows up in one location and not the others.

This one habit removes most false alarms from background job monitoring. Your team gets fewer pages, and the ones they get are real.

Step 5: Put the alert where it will be seen

A monitor is only as good as the alert path behind it. Decide, per job, who needs to know:

  • Critical jobs (backups, billing, payment webhooks) - on-call channel, immediate.
  • Important jobs (syncs, reports) - a team channel during working hours.
  • Low-stakes jobs - a daily digest, not a ping.

Plain-language messages help everyone here, especially on mixed teams. "The nightly client backup has not reported in 26 hours" is more actionable than a raw exit code, and it is the kind of wording you can forward to a client without translating it first.

Step 6: Review what actually happened

Once heartbeats are flowing, you have something you did not have before: a record of which jobs ran, when, and how long they took.

Use it monthly. Look for jobs that are creeping longer, jobs that miss their window occasionally, and jobs that are quietly failing and recovering on the next run. That last group is the dangerous one - it never triggers an alert, but it means something is wrong and the job is only succeeding by luck.

Agencies get a second benefit here. A plain-language monthly summary of scheduled tasks - what ran, what nearly failed, what was fixed - is an easy, honest touchpoint with clients who never see the technical layer at all.

Common mistakes to avoid

  • Monitoring the job's output instead of its heartbeat. Logs are for debugging. Heartbeats are for knowing it ran.
  • Pinging at the start of the job. You will miss crashes that happen mid-run.
  • No grace period. You will alert on normal timing variation and stop trusting the alerts.
  • One region only. A network blip looks exactly like a dead job.
  • Treating every job the same. Backups and a cache refresh do not deserve the same urgency.

Start with the jobs that matter most

You do not need heartbeat checks on everything today. Pick the three tasks whose silent failure would hurt the most - the backup, the billing run, the client data sync - and add a heartbeat to each this week. Set a reasonable window, verify failures across regions, and route the alerts to a place a human actually reads.

That is the whole process. Your site can stay green. Your background jobs just should not be invisible while it is. PulseKeeper's cron heartbeat checks fire an alert the moment a scheduled task stops reporting, so a silent failure becomes a plain-language message before anyone has to ask why last month's report is empty.

Monitor your client sites with PulseKeeper

Uptime from several regions, verified before anyone is alerted. Free to start, no card required.

More from the blog