CronPup

← Guides

Monitoring Node.js scheduled jobs (node-cron, BullMQ)

In-process schedulers like node-cron only run while your Node process is alive and has the schedule registered. A crash loop, a deploy that dropped the worker, or an exception swallowed by a promise means the job just stops, and nothing tells you. A heartbeat closes that gap: the job pings CronPup when it finishes, and you're alerted if the ping doesn't arrive on time.

A small helper (Node 18+)

// heartbeat.js
export async function ping(url) {
  try {
    await fetch(url, { signal: AbortSignal.timeout(5000) });
  } catch (err) {
    console.warn(`heartbeat ping failed: ${err.message}`); // never let monitoring break the job
  }
}

export async function monitored(url, fn) {
  await ping(`${url}/start`);
  try {
    const result = await fn();
    await ping(url);
    return result;
  } catch (err) {
    await ping(`${url}/fail`);
    throw err;
  }
}

node-cron

import cron from "node-cron";
import { monitored } from "./heartbeat.js";

cron.schedule("0 3 * * *", () =>
  monitored(process.env.BACKUP_PING_URL, runBackup),
  { timezone: "Europe/Berlin" });

Create a CronPup monitor with the cron expression 0 3 * * * and timezone Europe/Berlin, so it expects the run at the same moment.

BullMQ repeatable jobs

Wrap the processor, so the ping proves a worker actually ran the job, not just that it was queued:

new Worker("reports", async (job) => {
  if (job.name === "nightly-report") {
    return monitored(process.env.REPORT_PING_URL, () => buildReport(job.data));
  }
}, { connection });

Give the monitor a grace period that covers normal queue wait plus run time. Re-throwing keeps BullMQ's retries. A later successful attempt pings success and marks the monitor up again.

Several processes running the same schedule?

If every replica runs node-cron, each one pings. That's harmless for the heartbeat, but it usually means the job runs several times. Run the schedule in one process, or move it to a queue.

Monitor your Node.js jobs. 10 monitors are free.