← Blog  ·  October 2026

How to Monitor Cron Jobs and Get Alerted When They Fail

Set up monitoring once. Stop finding out about missed jobs from users.

Most cron jobs run without any monitoring. They work fine for months, so there's no urgency. Then one day a job stops running — a dependency breaks, a timeout gets hit, the scheduler itself crashes — and you don't find out until someone notices that the report wasn't generated, the cleanup didn't run, or the data is stale.

This guide covers the four practical approaches to cron job monitoring, with actual setup steps for each. By the end you'll have at least one of them running.

The four approaches:

1 Dead man's switch (heartbeat URL)

This is the most reliable approach for detecting missed schedules. The idea: your job makes an HTTP request to a "heartbeat" URL at the end of every successful run. A monitoring service watches for that ping on the expected schedule. If the ping doesn't arrive within the window, you get alerted.

It catches two failure modes that exit-code monitoring misses:

Setup with healthchecks.io (free tier)

Create a free account at healthchecks.io, create a check with your expected schedule, and copy the ping URL. Then add one line to your cron job:

# crontab entry
0 9 * * 1-5 /path/to/my-job.sh && curl -fsS --retry 3 https://hc-ping.com/your-uuid-here

The && means the ping only fires if the script exits 0. If the script fails, no ping. If healthchecks.io doesn't see a ping within your configured grace period, it emails you (or sends a Slack/webhook notification).

GitHub Actions equivalent

For scheduled workflows (on: schedule:), add a final step:

- name: Ping heartbeat
  if: success()
  run: curl -fsS https://hc-ping.com/your-uuid-here
One caveat with GitHub Actions schedules GitHub's scheduled workflows silently stop running on repos with no recent activity (approximately 60 days of inactivity). The scheduler doesn't alert you — the jobs just stop. If you're using on: schedule: for anything critical, add a heartbeat so you'll notice when this happens.

2 Wrapper scripts with on-failure notifications

Instead of modifying every individual cron job, wrap your jobs in a shell function that captures exit codes and fires notifications on failure. This approach works well when you have many jobs and don't want to change each one.

Basic bash wrapper

#!/bin/bash
# /usr/local/bin/cron-wrapper
# Usage: cron-wrapper "Job Name" /path/to/job.sh

JOB_NAME="$1"
shift

"$@"
EXIT_CODE=$?

if [ $EXIT_CODE -ne 0 ]; then
  MESSAGE="CRON FAILED: ${JOB_NAME} exited with code ${EXIT_CODE} at $(date)"
  
  # Option 1: Email (requires mailutils)
  echo "$MESSAGE" | mail -s "Cron failure: ${JOB_NAME}" you@example.com
  
  # Option 2: Slack webhook
  curl -s -X POST "$SLACK_WEBHOOK_URL" \
    -H 'Content-type: application/json' \
    -d "{\"text\":\"$MESSAGE\"}"
fi

exit $EXIT_CODE

Then update your crontab to use the wrapper:

0 9 * * 1-5 /usr/local/bin/cron-wrapper "Daily report" /path/to/report.sh
0 2 * * *   /usr/local/bin/cron-wrapper "Nightly cleanup" /path/to/cleanup.sh

This catches exit-code failures but doesn't detect the job failing to run at all. Combine it with the heartbeat approach for full coverage.


3 Log monitoring

If your jobs already write to log files, you can monitor the logs rather than the jobs themselves. There are two useful patterns:

Alert on error patterns

Tools like Papertrail, Datadog, Logtail, or self-hosted Grafana Loki can alert when log lines match patterns like ERROR, FATAL, or Traceback. Set up an alert that fires within a few minutes of a matching line appearing.

Alert on absence of expected output

More subtle: alert when a job that should have logged something doesn't. Most log monitoring tools support "absence alerts" — fire if no log line matching "daily-report: completed" appears between 9:00am and 9:30am.

# In your job script, log a completion marker:
echo "$(date -u +%Y-%m-%dT%H:%M:%SZ) daily-report: completed rows_processed=${ROW_COUNT}"

Then configure your log monitoring to alert if that marker doesn't appear on schedule.

Limitation: Log monitoring adds latency. By the time you're alerted, the job has already been missing for the duration of your alert window. For time-sensitive jobs, the heartbeat approach gives faster detection.

4 Dedicated cron monitoring service

If you have more than a handful of scheduled jobs, managing heartbeat URLs, wrappers, and log alerts becomes overhead in itself. Dedicated cron monitoring services handle all of this with a single integration:

The options

healthchecks.io — open source, free tier (20 checks), solid reliability. Good for most indie use cases.

Cronitor — more polished UI, better dashboards, paid from $10/mo. Worth it if you're monitoring 10+ jobs for a production product.

Dead Man's Snitch — dead simple, email-only on free tier, $5/mo for more. Very low friction to set up.

CronWatch (early access) — monitoring across crontab, GitHub Actions, Heroku Scheduler, and AWS EventBridge from a single dashboard. Designed for indie developers with jobs spread across multiple platforms.


Which approach should you use?

For a single critical job with no existing infrastructure: dead man's switch. One extra line in your crontab, five-minute setup, catches both failure modes.

For a large number of jobs on a single server: wrapper script + heartbeat for critical jobs.

For jobs spread across platforms (crontab, GitHub Actions, Heroku, AWS): dedicated service. The overhead of managing separate integrations per platform adds up fast.

📟 CronWatch monitors your jobs across every platform

One dashboard for crontab, GitHub Actions scheduled workflows, Heroku Scheduler, and AWS EventBridge. Get alerted the moment a job misses its window — without setting up separate integrations for each platform.

Join the CronWatch waitlist → Free cron expression parser

Quick-start checklist

Pick one and do it today:

  1. Identify your one most critical cron job — the one whose failure would cause the most damage if unnoticed for 24 hours
  2. Sign up for healthchecks.io (free), create a check with your job's schedule
  3. Add && curl -fsS https://hc-ping.com/YOUR_UUID to the end of that job's crontab entry
  4. Disable the job manually, wait for the grace period, verify you get the alert
  5. Re-enable the job and repeat for your next most critical job

The first one takes 10 minutes. After that it's mostly copy-paste. If you have more than five jobs or jobs on multiple platforms, CronWatch handles it from a single place.

Not sure what your cron expression actually does? Parse it here to see the plain-English description and next run times before you monitor it.