Back to sensacat

Home  /  Integration guides

· SensaCat Team

How to Monitor Cron Jobs in Node.js

node-cron runs inside your process. If the process dies, so does every schedule, and nothing reports it.

Monitoring scheduled work in Node.js depends on a choice most projects make without thinking about it: whether the scheduler runs inside your application process or outside it.

node-cron, node-schedule and Agenda all run in-process. That has a consequence worth being explicit about.

In-process schedulers share the fate of the process

If your application crashes, is OOM-killed, or is restarted by a deploy that fails halfway, the schedules go with it. There is no separate daemon holding them.

Worse, a process that is alive but has an event loop blocked by synchronous work will not fire timers either. The health endpoint may still answer because the request arrived before the block, and the process looks fine from outside while every schedule is frozen.

An external heartbeat is the only thing that notices, because it is the only participant that is not inside the process that stopped working.

Set the timezone explicitly. node-cron defaults to the system timezone, which is UTC in most containers and local time on a developer machine, and that difference produces schedules that work in testing and fire at the wrong hour in production.

Unhandled rejections in async callbacks

This is the Node-specific trap. If your scheduled callback is async and rejects without a catch, node-cron cannot await it and the rejection goes unhandled.

Depending on your Node version and flags, that either prints a warning or terminates the process. Neither outcome tells you which job failed, and the terminating case takes every other schedule down with it.

Always wrap the body of an async scheduled callback in try/catch, as above. Relying on a global unhandledRejection handler works, but it leaves you with an alert that does not say which job caused it.

PM2 restarts hide the problem

PM2 restarting a crashed process is useful and it removes the evidence. The application comes back, the schedules re-register, and unless you look at pm2 list you will not know it happened.

A restart count that grows steadily means jobs are being interrupted mid-run on a regular basis. A heartbeat with a tight interval surfaces this as missed runs rather than as a number nobody checks.

PM2 in cluster mode adds a second issue: every instance registers the same schedules, so a four-instance cluster runs each job four times. Either run the scheduler in a single dedicated process or gate it on process.env.NODE_APP_INSTANCE === '0'.

When to use system cron instead

In-process (node-cron) System cron
Survives app crash No Yes
Survives a blocked event loop No Yes
Shares app code and config Yes Needs a separate entry point
Duplicate runs in a cluster Likely No
Startup cost per run None A full Node boot

For anything that must not be missed, such as billing or reconciliation, system cron calling node script.js is the more robust arrangement despite the startup cost. Keep node-cron for lightweight work where a missed run is recoverable.

The system cron approach is covered in what is a cron job. Go has a similar in-process pattern, in monitoring cron jobs in Go.