All posts

March 19, 2026 8 min read

How to monitor cron jobs in Node.js (the right way)

node-cron and node-schedule don't tell you when your jobs fail. Here's how to add proper monitoring to any Node.js cron job — missed run detection, hung job alerts, and silent failure detection — in under five minutes.


You scheduled a Node.js cron job. It runs. You move on.

Three weeks later, you discover it stopped running two weeks ago and nobody noticed.

This is the default experience with cron job monitoring in Node.js, because the most popular scheduling libraries — node-cron, node-schedule, agenda — are schedulers, not monitors. They fire your function at the right time. They do not tell you when it fails, when it never starts, or when it runs for six hours instead of six minutes.

This guide covers how to add production-grade monitoring to any Node.js cron job.


Why node-cron and node-schedule don't provide monitoring

node-cron is the most popular Node.js scheduling library with over 3 million weekly npm downloads. Its job is to execute a function on a schedule. That's it. node-cron doesn't provide any built-in monitoring or alerting for failed or missed jobs. If your function throws an unhandled exception, node-cron logs it and moves on. No alert fires. No record is kept. The next scheduled execution happens as normal.

node-schedule has the same limitation. So does agenda, despite being a more full-featured job queue.

The issue is architectural. In-process schedulers only know what happens inside your process. They cannot detect:

  • A job that never started because the server rebooted
  • A job that exited with code 0 but processed zero records
  • A job that has been running for eight hours and is clearly stuck
  • A job that ran successfully on the last 14 executions but missed the last one

For those failure modes, you need an external monitoring service that watches your jobs from the outside — independent of your process.


The three failure modes you need to monitor for

1. Missed runs

Your job didn't start at all. Causes include server reboots, deployment interruptions, process crashes, and crontab corruption. Without external monitoring, you will not know until a downstream effect (missing data, unsent emails, stale reports) surfaces.

2. Hung jobs

Your job started but hasn't finished. Deadlocks, infinite loops, stuck database queries, and network timeouts all cause this. A hung job doesn't crash — it just runs forever, consuming memory and blocking other work. node-cron has no concept of a maximum runtime.

3. Silent failures

Your job completed with exit code 0 but accomplished nothing useful. A sync that processed zero records. A cleanup that deleted nothing. An email batch that sent to an empty list. This is the hardest failure to catch and the one that does the most damage before you notice.


How to add monitoring to a node-cron job

Install the Crontify SDK:

npm install @crontify/sdk

Before — unmonitored:

import cron from 'node-cron';

cron.schedule('0 2 * * *', async () => {
  await syncDatabase();
});

After — fully monitored:

import cron from 'node-cron';
import { CrontifyMonitor } from '@crontify/sdk';

const monitor = new CrontifyMonitor({
  apiKey: process.env.CRONTIFY_API_KEY!,
  monitorId: 'your-monitor-id', // from the Crontify dashboard
});

cron.schedule('0 2 * * *', async () => {
  await monitor.wrap(async () => {
    await syncDatabase();
  });
});

wrap() does three things automatically:

  1. Calls start() when the function begins, creating a run record
  2. Calls success() when it completes, recording duration
  3. Calls fail() if it throws, attaching the error message and stack trace

If the cron job never fires — because the server rebooted, for example — the Crontify scheduler detects the missed start ping and fires an alert.


Adding silent failure detection with output metadata

The wrap() API accepts metadata you return from your function. This metadata can trigger alert rules you define in the Crontify dashboard:

cron.schedule('0 2 * * *', async () => {
  await monitor.wrap(async () => {
    const result = await syncDatabase();

    // This metadata is attached to the run record
    // and evaluated against your alert rules
    return {
      meta: {
        rows_synced: result.count,
        duration_ms: result.durationMs,
        errors_encountered: result.errors,
      }
    };
  });
});

In the dashboard, you can define rules like:

  • rows_synced eq 0 → fire alert (job ran but synced nothing)
  • errors_encountered gt 0 → fire alert (partial failure)
  • duration_ms gt 300000 → fire alert (took longer than 5 minutes)

The run is still logged as a success. Your success rate is not affected. But you get an immediate Slack or email notification when something is wrong — even when the job technically completed.

This is the class of failure that node-cron itself, and most monitoring tools, cannot catch.


Attaching log output to failed runs

When a job fails, the error message alone is often not enough. You want the full stack trace, the database query that failed, the API response that triggered the exception.

cron.schedule('0 2 * * *', async () => {
  await monitor.start();

  try {
    const result = await syncDatabase();
    await monitor.success({
      meta: { rows_synced: result.count }
    });
  } catch (err) {
    // Attach up to 10,000 characters of log output
    // First 500 chars appear inline in Slack/email alerts
    await monitor.fail({
      message: err.message,
      log: err.stack,
    });
  }
});

Monitoring node-cron jobs without the SDK

If you prefer not to use an npm package, every Crontify monitor exposes three plain HTTP endpoints:

# In a bash cron job
MONITOR_ID="your-monitor-id"
API_KEY="ck_live_..."
BASE="https://api.crontify.com/api/v1/ping"

# Signal start
curl -fsS -X POST "$BASE/$MONITOR_ID/start" \
  -H "X-API-Key: $API_KEY"

# Run your job
node /path/to/script.js
JOB_EXIT=$?

# Signal result
if [ $JOB_EXIT -eq 0 ]; then
  curl -fsS -X POST "$BASE/$MONITOR_ID/success" \
    -H "X-API-Key: $API_KEY"
else
  curl -fsS -X POST "$BASE/$MONITOR_ID/fail" \
    -H "X-API-Key: $API_KEY"
fi

This works for any language or runtime. Python, Go, Ruby, PHP — anything that can make an HTTP request.


What to monitor and what to skip

Not every cron job needs external monitoring. A rough guide:

Always monitor:

  • Database backups
  • Data sync and ETL jobs
  • Email and notification dispatch
  • Billing and payment processing
  • Any job where failure affects users or data integrity

Optional:

  • Cache warming (failure is usually recoverable)
  • Log rotation (failure is non-urgent)
  • Analytics aggregation (delayed, not missing)

Skip:

  • Jobs that run every minute (monitoring overhead adds up; only monitor if every single execution matters)
  • Jobs where failure is immediately visible in the UI (these have natural detection built in)

Frequently asked questions

Does monitoring add latency to my cron jobs?

No meaningful latency. The Crontify SDK fires pings asynchronously by default. start() and success() calls complete in under 50ms in typical conditions, and the SDK handles timeouts silently — a slow or failed ping never blocks your job from running.

What happens if Crontify itself goes down?

Your jobs keep running. The SDK handles ping failures silently by default — if a ping cannot reach Crontify, your job continues normally. The silent: true option (which is the default) ensures a monitoring outage never causes a job failure.

Can I monitor the same job running in multiple instances?

Yes. Each instance sends pings to the same monitor ID. Crontify detects overlapping runs — when a new instance starts before the previous one has reported success or failure — and fires an overlap alert.

How do I set the grace period?

The grace period is configured per monitor in the Crontify dashboard. It's the amount of time Crontify waits after the expected start time before declaring a missed run. For a job that runs at 2am and typically takes 20 minutes, a grace period of 30 minutes is reasonable.


Start monitoring for free

Crontify is free for up to 5 monitors — no credit card required.

crontify.com — SDK on npm as @crontify/sdk.

Add monitoring to your Node.js cron jobs in under five minutes.


Start monitoring your scheduled jobs

Free plan includes 5 monitors. No credit card required. Up and running in under 5 minutes.

Get started free →