24observe
checking… Start free
Cron job monitoring 2026-05-15 14 min read

How to monitor a cron job in 2026 (without losing a night's sleep)

Most cron job outages do not look like outages. They look like a quiet Tuesday afternoon, a Slack channel where nobody is panicking, and a finance team that realises three weeks later that the nightly invoice job has not run since the long weekend. This is a complete 2026 guide to making sure that day never arrives at your company.

The 3 a.m. call no one wants

Picture it. You ship a new internal tool on a Friday. The team is small, the launch goes well, you take the weekend. On Monday morning you check the dashboards out of habit. Everything green. Response times are healthy. The new tool has its first cohort of internal users. You feel good.

On Wednesday afternoon, someone from finance pings you. "Hey, quick question — do we still send the weekly revenue summary?" You check. The weekly job is scheduled to run every Sunday at 7 a.m. It did not run on Sunday. It also did not run the Sunday before. The Slack channel where it normally posts has been quiet for two weeks and nobody noticed, because nobody is sitting around watching a Slack channel for the absence of a message.

This is the most common pattern in cron job incidents. It is not a 3 a.m. page. It is a slow, embarrassing realisation a week later that something that was supposed to happen on a schedule simply did not happen, and your team learned about it from someone outside engineering. The cost is rarely measured in downtime minutes. It is measured in trust, in the credibility of the data your business runs on, and in the time it takes to reconstruct the missed work after the fact.

~40%
of cron failures discovered by someone outside the engineering team
5-14 days
typical time between failure and discovery for "silent" cron jobs
1 line
of code is the typical fix once you have heartbeat monitoring in place

We have spent the last two years talking to teams about how their scheduled jobs fail. The same five patterns come up again and again, and the good news is that every one of them is solvable today with a small change to how you instrument your jobs. This guide walks through what those patterns are, why classic monitoring approaches miss them, and how modern cron job monitoring actually works in 2026.

Why cron jobs fail silently

Web services are noisy when they break. A user hits an endpoint, gets a 500, complains in support, and an alert fires from your error tracker within seconds. There is a feedback loop between failure and discovery, and that loop is built into the way users interact with your product.

Cron jobs do not have a user on the other end. There is no one refreshing the page hoping for a response. When a backup job exits 1 because the disk filled up, there is nobody to complain. When a syncing job loses connectivity to a downstream API and exits cleanly with "nothing to do" in its logs, it looks indistinguishable from a healthy run. When the cron daemon itself stops, every job on that host stops with it, and the only signal is the absence of activity that nobody was watching for.

Here are the failure modes we see most often, in rough order of how frequently they bite teams:

  • The job runs but does nothing useful. A change upstream — a renamed table, an expired API key, a deleted bucket — turns a daily job into a daily no-op. Logs say "0 rows processed." Nobody looks at the logs.
  • The job exits 0 on partial failure. A script processes 4 of 5 input files, the 5th throws an exception, but a stray try/except swallows it and the script exits cleanly. From the outside, everything is fine.
  • The schedule fires but the worker is gone. The container that ran the job was redeployed, evicted, or never scheduled by the orchestrator. There is no log line because the process never ran.
  • The job runs too late to matter. A weekly report that is supposed to land Monday morning runs Tuesday afternoon because a dependency was slow. Nobody noticed it was missing until the meeting.
  • Two copies of the job overlap. The 2 a.m. job takes 90 minutes; the 3 a.m. job starts before the first one finishes; they fight over the same database lock and one of them crashes silently.

None of these are exotic. Every team we have worked with has hit at least three of them. What they have in common is that classic monitoring — error trackers, log dashboards, even mature observability platforms — cannot detect the absence of an event. They are built to react to signals that arrive. Cron monitoring is the inverse: it reacts to signals that do not.

The "did it run?" trap

When teams first build cron monitoring in-house, the instinct is almost always the same. Wrap the job in something that logs "started" at the top and "finished" at the bottom. Push those log lines into your log pipeline. Set up a query that says "alert me if 'finished' did not appear in the last 24 hours."

This works. For about three weeks.

Then the log pipeline has a delivery hiccup and the "finished" line drops. Or a developer renames the log message and forgets to update the alert query. Or the cron schedule changes from daily to weekly and the alert keeps using a 24-hour window. Or — most commonly — the alert never fires because the log message did get through, but the job itself produced an empty result. The job ran. It just ran wrong.

The "did it run?" model misses the deeper question, which is: "did the work complete and was the work correct?" A proper cron monitor needs to answer both. It needs to know that a heartbeat arrived within a window, and that the heartbeat came from a successful run, and that the time between runs has not drifted beyond what you consider acceptable.

The shift: stop asking "did my cron daemon execute this line?" and start asking "did my business outcome happen this period?" The first question is a system question. The second is the only one that matters to the team that depends on the work.

What good monitoring looks like in 2026

A modern cron monitoring setup answers four questions, all the time, for every scheduled job you care about:

  1. Did it run when it was supposed to? Was the heartbeat received inside the interval plus grace window?
  2. Did it finish successfully? Did the heartbeat ship after the work completed, or did it ship optimistically at the start of the script?
  3. Did it run within a reasonable duration? Is the job's runtime drifting upward in a way that suggests something is degrading?
  4. Is the right team being told? When something is wrong, does the alert go to the on-call rotation for the team that owns this work, not just to a shared inbox nobody checks?

The good news is that all four are achievable with a small amount of plumbing — a single HTTP request from the end of your script and a monitor configured once in a dashboard. The hard part is no longer the technology. The hard part is making sure every job that matters has the plumbing in place and that no one ever ships a new scheduled job without it. We will come back to that organisational piece in the pitfalls section.

There is a vocabulary worth learning here. The pattern is sometimes called a "dead man's switch" — a mechanism that triggers when something stops happening rather than when it happens. In the cron world it is more commonly called heartbeat monitoring, and the alert that fires when a heartbeat is missing is called a missed check-in. Different tools use slightly different language but the shape is the same.

Where cron actually lives in 2026

One reason older guides on this topic feel dated is that they assume "cron" means /etc/crontab on a Linux server. That is still true for plenty of teams, but most modern stacks have at least three or four different places where scheduled work happens. A real monitoring strategy needs to cover all of them.

Classic cron and systemd timers

Still going strong. Crontab is the simplest scheduler in the world and that is its superpower. systemd timers are the modern Linux replacement with better logging, dependency management, and a saner debugging story. Heartbeat monitoring works identically for both — your script ends with a single curl to the monitor URL.

Kubernetes CronJobs

If you run anything substantial on Kubernetes, you have CronJob resources. They give you scheduling, retry semantics, history limits, and concurrency policy. They do not give you "did the actual work succeed in business terms?" — the pod can exit 0 while the script inside did nothing useful. Heartbeat monitoring is the layer that closes that gap, and it lives at the script level, not the pod level.

Managed cloud schedulers

AWS EventBridge Scheduler, Google Cloud Scheduler, and Azure Logic Apps all run scheduled invocations of cloud functions and APIs. They have their own dashboards for invocation success, but again — they tell you the function returned 200, not that the work inside the function actually succeeded. The heartbeat pattern transposes cleanly.

Serverless scheduled functions

Vercel Cron, Cloudflare Workers Cron Triggers, Netlify Scheduled Functions, and the equivalents in Render and Fly all let you say "run this function every N minutes." They are some of the easiest schedulers to use today. The same heartbeat pattern fits — the function does its work and POSTs a heartbeat at the end.

CI-driven cron

GitHub Actions has schedule: triggers. GitLab has scheduled pipelines. Many teams have quietly migrated their "we run a thing every night" workloads into their CI provider because it was easier than provisioning a server. CI providers are reasonable about telling you when a workflow run failed, but they are silent when the schedule itself was skipped — which can and does happen during platform incidents. Heartbeat monitoring catches those.

The pattern is the same in every environment. The schedule fires, the work runs, the work finishes, the script POSTs to a heartbeat URL. The monitoring service does not care where the work executed. That decoupling is what makes heartbeat monitoring durable across the next decade of compute changes — when you move from EC2 to a container platform to something we have not invented yet, the monitor stays exactly where it is.

Heartbeat monitoring, explained without the buzzwords

A heartbeat monitor is a URL plus a clock. You configure two things — an interval (how often you expect the heartbeat) and a grace period (how much late is acceptable before an alert). The monitoring service then waits for HTTP requests to that URL.

Every time your job finishes successfully, it sends a single POST or HEAD to the URL. The monitoring service records the time. If the next request does not arrive within interval + grace, the service opens an incident, alerts your configured channels, and waits. When the next successful heartbeat does arrive, the incident auto-resolves and your team gets a recovery notification.

Why this is more durable than log-based monitoring

Log pipelines have their own failure modes. They can drop messages, fall behind, change format, or be reconfigured in ways that break old alert queries. A heartbeat URL has one job: receive a request. There is no parsing, no log shipper to maintain, no buffer to fill, no index to query. The whole signal is one HTTP request and one timer.

There are a few subtleties worth getting right from the start:

  • Ship the heartbeat after the work, not before. If you POST at the top of your script and the script crashes halfway through, you have an optimistic heartbeat that lies about the state of the world. The first line of the script is the worst place to ship a heartbeat.
  • Pick a grace period larger than your worst-case runtime. If your daily job usually runs in 90 seconds but occasionally takes 8 minutes when the source data is large, your grace should be at least 10 minutes. A grace period that is too tight produces false alerts during normal slow runs and trains your team to ignore them.
  • Use a per-job URL, not a shared one. Each scheduled task gets its own monitor with its own interval and its own alert routing. Sharing a URL across jobs makes incidents ambiguous and routing impossible.
  • Send only on success. If your script can detect partial failure, do not ship the heartbeat. Let the monitor go silent and the missed-check-in incident will fire. This is the cheapest way to get "the work was correct" into your alerting without writing custom failure-state logic.
  • Treat the heartbeat URL as a secret. Anyone with the URL can mark your monitor as up, which means they can hide an outage from you. Do not put it in client-side code, do not commit it to public repos, and rotate it if it leaks.

Wiring up your first heartbeat (3 minutes)

Concrete is better than abstract. Here is the entire end-to-end setup using 24observe as the example monitoring service, because that is what we know best. The exact same shape works with every reputable cron monitoring tool on the market — only the URL host and the dashboard UI change.

Step 1: Create a heartbeat monitor

Either click "New monitor" in the dashboard and choose type "Heartbeat", or call the API. We will show the API path because it is the one that scales when you have dozens of jobs and you want to provision them from infrastructure-as-code.

curl -X POST https://api.24observe.com/api/v1/monitors \
  -H 'Authorization: Bearer obs_<your-token>' \
  -H 'Content-Type: application/json' \
  -d '{
    "name": "Nightly invoice job",
    "type": "heartbeat",
    "intervalSec": 86400,
    "heartbeatGraceSec": 600,
    "alertEmail": "[email protected]"
  }'

That creates the monitor and returns a unique heartbeat URL. The interval is in seconds (one day), the grace gives you 10 minutes of slack before an alert fires.

Step 2: Add one line to the end of your job

Whatever your job is written in, the end-of-script call is the same shape. A few examples:

# Bash
./run-nightly-invoices.sh && curl -fsS -X POST https://api.24observe.com/api/v1/heartbeats/<TOKEN>

# Python
import httpx
do_the_work()
httpx.post("https://api.24observe.com/api/v1/heartbeats/<TOKEN>")

# Node.js
await doTheWork();
await fetch("https://api.24observe.com/api/v1/heartbeats/<TOKEN>", { method: "POST" });

The shell && is the important bit. It only runs the curl if the previous command exited 0. If your job fails, the heartbeat does not ship, the monitor goes quiet, the incident opens.

Step 3: Wait one cycle, watch it light up

Run your job. Within a few seconds the monitor's dashboard shows the heartbeat arrived. If the next expected heartbeat does not arrive on time, you get an email (or Slack, Discord, Teams, Telegram, webhook — whatever you configured). That is the entire setup.

Step 4: Pause for maintenance

When you deploy and you know the job will not run for the next hour, pause the monitor from the dashboard or API. Missed pings during a pause do not create incidents. Resume when the maintenance window closes.

Picking a tool: honest comparison

There are several excellent cron monitoring services in 2026, and we genuinely respect the teams behind all of them. The category exists because Healthchecks.io proved the idea works at scale; the rest of us built on that. Here is an honest snapshot of the major options, what each does well, and where 24observe is a stronger fit.

Tool Best for Free tier Notable strength
Healthchecks.io Solo devs and small teams who want a focused tool 20 monitors Open source, can self-host, beautifully simple UI
Cronitor Teams that want richer cron analytics 5 monitors Strong scheduling/duration analytics out of the box
BetterStack Teams already on BetterStack for uptime and logs Included with monitoring plans One bill for uptime, logs, and heartbeats together
UptimeRobot Existing UptimeRobot uptime customers Heartbeat included with free tier Huge install base, mature alert routing
24observe Teams managing monitors through code, CI, or AI agents Generous free tier with API-first access Heartbeat tokens are encrypted at rest, OpenTelemetry-native, designed for programmatic provisioning

If you are already on one of the established tools and it is serving you well, that is a perfectly good answer and there is rarely a reason to migrate for migration's sake. We built 24observe for a specific gap: teams where engineers and AI agents both need to provision, modify, and decommission monitors as part of normal workflows. Every capability in our dashboard is available through the same API your humans use, with the same authentication and the same audit trail. If you have ever wanted your deployment pipeline to create heartbeat monitors automatically, or your AI agent to spin up a temporary monitor for a one-off batch job, that is the world we are designed for.

We are also deliberately positive about open-source alternatives. If you want the simplest possible tool and you are comfortable self-hosting, Healthchecks.io is excellent and we recommend it without hesitation. Our value is on the other side of the spectrum — managed, multi-region, API-first, with logs and uptime checks in the same product so you have one credential and one billing relationship for the things that observe your infrastructure.

Pitfalls that still bite teams in 2026

We have been collecting these from customer conversations and our own incident reviews. None of them are exotic; all of them are still happening to good teams this year.

Pitfall 1: A grace period that is too tight

The most common false alert. Your job is supposed to run every 5 minutes, you set grace to 30 seconds, and one day your upstream API is sluggish for 4 minutes. The monitor fires, the on-call gets paged, the job recovers on the next cycle. Train this happens often enough and your team stops trusting cron alerts entirely. Pick a grace period that comfortably covers your worst-case runtime and the worst-case latency of any service your job depends on.

Pitfall 2: Shipping the heartbeat at the start of the script

"I'll just put it at the top so I do not forget" sounds reasonable until the script panics on line 3 and the heartbeat has already shipped. You have monitoring that confirms the script started, not that it finished. The fix is one line: move the heartbeat to the end and only execute it after success.

Pitfall 3: Timezones

A scheduler in UTC and a team that thinks in local time is a perfect recipe for "we expected this at 8 a.m. and it ran at 1 a.m." This bites especially hard around daylight saving transitions. Pick UTC for everything and let your monitoring tool display in local time. Document this once in a README and never argue about it again.

Pitfall 4: Routing alerts to a shared inbox

"Sends to ops@" is the cron monitoring equivalent of putting your alerts in a drawer. Use a routing layer — Slack with @channel mentions for the right squad, a paging tool with on-call rotations, or both. The cost of a missed-check-in alert that nobody saw is the same as having no monitoring at all.

Pitfall 5: Monitoring the schedule, not the work

A job can finish "on time" and produce nothing. The most defensive cron monitoring setups include a separate check that the work itself happened — a query that confirms last night's backup table has data, or that the synced records exist downstream, or that the report file landed in the bucket. A simple heartbeat plus a separate result-check monitor is the gold standard for jobs that produce critical business outcomes.

Pitfall 6: No process for adding monitors to new jobs

The job that is going to bite you in six months is the one a teammate ships next week without instrumenting it. The fix is organisational: a pre-merge checklist item, a deployment-pipeline assertion, a template repository. Whatever your team's culture supports. The technology is easy; the policy is the part that protects you over time.

FAQ

What is cron job monitoring?

Cron job monitoring is the practice of confirming that a scheduled task actually ran, finished, and produced the result it was supposed to. It typically uses a small "heartbeat" HTTP request from the job to a monitoring service. If the heartbeat does not arrive within an expected window, the monitoring service opens an incident and alerts you. It is distinct from generic uptime monitoring, which checks that a URL responds — cron monitoring asks the inverse question: did something happen that should have happened?

How do I monitor a cron job that runs every 5 minutes?

Create a heartbeat monitor with a 5-minute interval and a small grace period (60 seconds is a sensible default). Each successful run of your job sends a single HTTP POST to the unique heartbeat URL the monitoring service gives you. If the next ping does not arrive within interval + grace, an incident opens. The same pattern works for any cadence — 30 seconds, hourly, daily, monthly — by adjusting the interval and grace.

What is the difference between cron job monitoring and uptime monitoring?

Uptime monitoring asks "is this URL responding?" by making outbound requests to your service. Cron monitoring inverts that: your job makes a request to the monitoring service after it succeeds. The two solve different problems and most production teams need both. A cron monitor is the only way to detect a job that silently failed to run at all — uptime checks cannot see something that does not exist.

Do I need cron monitoring if my jobs run inside Kubernetes?

Yes. Kubernetes CronJobs solve scheduling and retry, but they do not solve "is this still doing the right thing in production at 3 a.m.?" A Kubernetes CronJob will happily mark a pod as succeeded when the script exits 0 even if the underlying work — a backup, a report, a sync — produced nothing useful. Heartbeat monitoring is the layer that asks the work itself to confirm success.

What happens when my cron job is genuinely down for maintenance?

Most heartbeat monitoring tools, including ours, support pausing a monitor for planned maintenance windows. You can pause via the dashboard, the API, or a CLI in your deploy pipeline. When paused, missed pings do not generate incidents. Resume the monitor when maintenance is over and normal alerting picks up from the next expected ping.

How do I keep my heartbeat URL secret?

Treat the heartbeat URL like a password. Do not commit it to a public repo, do not log it in plain text, and do not put it in client-side code. Mature monitoring services give you a way to rotate the URL if it leaks, and store only a hash of the token server-side so that even a database leak on the vendor side does not expose live URLs. If you ship through CI, inject the URL via your secret store rather than baking it into the script.

Can I monitor jobs running on serverless platforms like Vercel or Cloudflare?

Yes, and the pattern is identical. The serverless scheduler invokes your function on a schedule; your function ends with a single HTTP request to the heartbeat URL. The monitoring service does not need to know anything about how the function is hosted. Some teams prefer a wrapper utility that POSTs the heartbeat after their main logic completes, so the heartbeat ships only on success, not on partial failure.

Three minutes to your first heartbeat

Sign up, create a heartbeat monitor, paste one line of curl at the end of your cron job. Watch the monitor go green. Forget about it for the next year — except for the one time it pages you about a job that quietly stopped, which is the entire point.