DEV Community

Vigilmon
Vigilmon

Posted on

Cron Job Monitoring: How to Get Alerted When a Scheduled Task Fails or Doesn’t Run

Cron jobs are the unsung workers of every production system. They run database backups at 2 AM, generate weekly reports, flush email queues, and sync data between services. Most of the time, they work fine. Until they don't.

The dangerous part isn't when a cron job crashes with a visible error — it's when it silently stops running. Maybe a server was rebooted and the crontab wasn't restored. Maybe a broken dependency caused the script to exit 0 without actually doing anything useful. Maybe the scheduled container never started. Nobody knows. The backup that was supposed to run every night hasn't run in three weeks. You only discover this when someone asks for a restore.

This is the silent failure problem, and it affects every team that relies on scheduled tasks.

What Is Heartbeat Monitoring?

Traditional uptime monitoring works by pinging out — a monitoring service pings your server every N seconds and alerts you if it doesn't get a response. This works well for web servers and APIs, but it doesn't help with scheduled tasks that only run occasionally.

Heartbeat monitoring flips the model. Instead of the monitor reaching out to your service, your task pings in to the monitor after it completes. You set an expected interval — say, every 24 hours — and if that ping doesn't arrive within the grace period, the monitor fires an alert.

  • Ping-out model: Monitor → Your endpoint (good for always-on services)
  • Ping-in model (heartbeat): Your task → Monitor URL (good for scheduled jobs)

The ping-in model is the right tool for cron jobs because:

  • The monitor doesn't need network access to your server
  • It's language-agnostic — any HTTP request works
  • It catches both crashes and missed runs (the job never started)
  • It works for containers, serverless functions, and CI pipelines

Adding Heartbeat Monitoring to Your Scheduled Tasks

Linux Cron

The simplest integration is a one-liner appended to your cron command. After your script exits successfully, curl a heartbeat URL:

# Without heartbeat (invisible failures)
0 2 * * * /usr/local/bin/backup.sh

# With heartbeat ping on success
0 2 * * * /usr/local/bin/backup.sh && curl -fsS --retry 3 https://hb.vigilmon.online/YOUR_MONITOR_ID > /dev/null 2>&1
Enter fullscreen mode Exit fullscreen mode

The && ensures the ping only fires if the script exits with code 0. The -fsS --retry 3 flags make curl fail silently on errors and retry on transient network issues — so a failed backup won't accidentally mark itself as healthy.

For two-state monitoring (pinging on both success and failure):

0 2 * * * /usr/local/bin/backup.sh \
  && curl -fsS "https://hb.vigilmon.online/YOUR_MONITOR_ID?status=ok" \
  || curl -fsS "https://hb.vigilmon.online/YOUR_MONITOR_ID?status=fail"
Enter fullscreen mode Exit fullscreen mode

Python

For Python scripts, use the requests library to send the heartbeat at the end of your main function:

import requests
import sys

HEARTBEAT_URL = "https://hb.vigilmon.online/YOUR_MONITOR_ID"

def run_job():
    # your job logic here
    generate_report()
    sync_to_s3()

if __name__ == "__main__":
    try:
        run_job()
        requests.get(HEARTBEAT_URL, timeout=10)
        print("Job complete, heartbeat sent")
    except Exception as e:
        requests.get(f"{HEARTBEAT_URL}?status=fail", timeout=10)
        print(f"Job failed: {e}", file=sys.stderr)
        sys.exit(1)
Enter fullscreen mode Exit fullscreen mode

This wraps your job in a try/except, sends a success ping on completion, and a failure ping if anything raises. If you use APScheduler or Celery Beat, hook into the job's success/failure callbacks and fire the requests there.

Node.js (node-cron + axios)

For Node.js scheduled tasks using node-cron:

const cron = require('node-cron');
const axios = require('axios');

const HEARTBEAT_URL = 'https://hb.vigilmon.online/YOUR_MONITOR_ID';

cron.schedule('0 2 * * *', async () => {
  try {
    await runBackup();
    await axios.get(HEARTBEAT_URL, { timeout: 10000 });
    console.log('Backup complete, heartbeat sent');
  } catch (err) {
    // Nested catch so heartbeat errors don't mask the real error
    await axios.get(`${HEARTBEAT_URL}?status=fail`, { timeout: 10000 }).catch(() => {});
    console.error('Backup failed:', err.message);
  }
});

async function runBackup() {
  // your backup logic here
}
Enter fullscreen mode Exit fullscreen mode

Laravel Scheduled Tasks

Laravel's task scheduler has built-in pingOnSuccess and pingOnFailure methods:

// app/Console/Kernel.php
protected function schedule(Schedule $schedule)
{
    $schedule->command('db:backup')
        ->daily()
        ->pingOnSuccess('https://hb.vigilmon.online/YOUR_MONITOR_ID')
        ->pingOnFailure('https://hb.vigilmon.online/YOUR_MONITOR_ID?status=fail');
}
Enter fullscreen mode Exit fullscreen mode

Laravel handles the HTTP request automatically. Just ensure guzzlehttp/guzzle is installed (it ships with Laravel by default).

GitHub Actions Scheduled Workflows

CI pipelines that run on a schedule are also cron jobs, and they fail silently too. Add a heartbeat step at the end of your workflow:

name: Nightly Data Sync

on:
  schedule:
    - cron: '0 2 * * *'

jobs:
  sync:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Run data sync
        run: python sync.py

      - name: Send heartbeat (success)
        if: success()
        run: curl -fsS https://hb.vigilmon.online/YOUR_MONITOR_ID

      - name: Send heartbeat (failure)
        if: failure()
        run: curl -fsS "https://hb.vigilmon.online/YOUR_MONITOR_ID?status=fail"
Enter fullscreen mode Exit fullscreen mode

The if: success() and if: failure() conditions ensure the right signal is sent regardless of which step failed.

Setting Up a Heartbeat Monitor in Vigilmon

Getting started takes under two minutes:

  1. Create an account at vigilmon.online — the free tier includes heartbeat monitors
  2. Add a new monitor and select Heartbeat as the monitor type
  3. Set the expected interval — if your cron runs every 24 hours, set interval to 24 hours with a 30–60 minute grace period
  4. Copy your unique heartbeat URL — it looks like https://hb.vigilmon.online/abc123
  5. Add the URL to your cron job using one of the examples above
  6. Configure your alert channel — email, Slack, PagerDuty, or webhook

After your first successful ping, the monitor turns green. If a ping doesn't arrive within interval + grace period, Vigilmon fires your configured alert.

When Does It Alert?

Vigilmon alerts you in two situations:

Missed run — Your job didn't ping within the expected window. This catches: server reboots, crontab misconfiguration, containers that didn't start, scheduler processes that died, and network outages during the run window.

Explicit failure — Your job pinged with ?status=fail. This catches: script exceptions, non-zero exit codes, data validation failures, and any condition you decide to treat as an error in your own code.

You can configure escalation — send a Slack notification immediately, then page on-call if unacknowledged after 15 minutes.

Real-World Use Cases

Database backups — The highest-stakes cron job. If your nightly backup stops running for a week and nobody notices until you need a restore, the consequences are severe. A heartbeat monitor is your last line of defense.

Report generation — Weekly sales reports, monthly billing summaries. When these silently fail, the window to regenerate them cleanly often closes before anyone realizes something is wrong.

Email queues — Many applications process queued emails via a cron job. If it stops, users stop receiving transactional emails. No HTTP error, no server alert — just silence and confused customers opening support tickets.

Data sync pipelines — ETL jobs, third-party API syncs, inventory updates. Silent failures here cause data drift that can take days to diagnose and reconcile.

SSL certificate renewalcertbot renew runs as a cron job. If it fails silently, your certificate expires and users see browser security warnings with no obvious server-side error to trace.

Stop Flying Blind on Your Scheduled Tasks

Cron jobs are mission-critical infrastructure, but they're invisible by default. One curl command appended to each scheduled task gives you a complete picture of your background job health — and alerts you (not your users) the moment something goes wrong.

Start monitoring your cron jobs free at vigilmon.online

Top comments (0)