All posts

Cron in Node.js: Setup, Options, and the Reliability Gap

August 12, 2026

Every Node.js developer eventually needs a task that runs on a schedule — clearing stale sessions, syncing data, sending digest emails. The instinct is to reach for an npm package, write a cron expression, and move on. That gets you scheduling. It doesn't get you reliability, and the two are easy to confuse until a job silently stops firing and nobody notices for a week.

This guide covers the real options for cron in Node.js, a working node-cron example, an honest comparison of the popular libraries, and — because this is the part most tutorials skip — what actually happens when these schedulers fail quietly in production.

Ways to Run Cron Jobs in Node.js

There are two fundamentally different approaches, and conflating them causes most of the confusion around "cron nodejs" as a search term.

System cron calling a Node script uses your OS's native crontab to run node /path/to/script.js at a scheduled time. The process starts, runs, and exits — cron itself, not Node, tracks the schedule. This is simple and survives app restarts because it's decoupled from your application process entirely.

In-process scheduling libraries — node-cron, cron, node-schedule, agenda — run inside your live Node application. You register a cron expression and a callback, and the library's internal timer fires the function while your app is running. This is more convenient for app-embedded logic but ties the job's fate directly to your process's uptime, which is the crux of everything below.

Most teams reaching for "cron nodejs" content want the second approach, so that's the focus here.

Scheduling a Job with node-cron: A Basic Example

node-cron is the most widely used in-process scheduler for Node, largely because its syntax mirrors standard crontab. Install it:

npm install node-cron

Then schedule a task using a standard Node.js cron expression:

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

cron.schedule('*/5 * * * *', () => {
  console.log('Running cleanup job:', new Date().toISOString());
  // your task logic here
});

The expression */5 * * * * runs every five minutes, using the same five-field format as Linux cron (minute, hour, day-of-month, month, day-of-week). node-cron also validates expressions and supports timezones via a timezone option, which matters if your servers and your business logic don't share a clock.

That's a complete, working schedule. It's also, on its own, a black box — it tells you nothing if the callback throws, hangs, or never runs.

node-cron vs cron vs node-schedule vs agenda: Which One to Use

These four packages solve overlapping but distinct problems, and picking based on GitHub stars alone tends to lead to a mismatch.

Package Syntax Persistence Best fit
node-cron Standard cron expressions In-memory only Lightweight, single-process scheduled tasks
cron Cron expressions + Date objects In-memory only Fine-grained control, one-off + recurring jobs
node-schedule Cron expressions, dates, recurrence rules In-memory only Complex recurrence logic (e.g., "last Friday of month")
agenda JS scheduling API, not cron syntax MongoDB-backed Job queues needing retries, persistence, priorities

The cron npm package and node-cron are often confused because of the name overlap — they're separate libraries with similar but not identical APIs. node-schedule adds human-readable recurrence rules on top of cron syntax, useful when your schedule logic is genuinely irregular. Agenda is the outlier: it persists jobs to MongoDB, so scheduled work survives a restart and supports retry logic natively — at the cost of a database dependency the other three don't need.

For a broader sense of adoption and how these compare against queue-based options like Bull and Bree, npm trends data is a useful reference point. But adoption numbers don't tell you about failure visibility — none of these tools, agenda included, notify a human when a job errors out or fails to run.

Where In-Process Node.js Cron Jobs Fall Apart

This is the part that rarely makes it into setup tutorials, and it's the actual reason teams end up debugging a "job didn't run" incident at 2am.

Process crash means the job is gone. If your Node app restarts — a deploy, an OOM kill, a container reschedule — every in-memory schedule with node-cron, cron, or node-schedule disappears until the process comes back up. There's no queue catching up on missed runs; the schedule simply resumes from whenever the process restarts.

Overlapping runs on long tasks. If a job takes longer than its interval — a five-minute cron running a job that occasionally takes seven minutes — you can get two instances executing concurrently unless you guard against it. Recent versions of node-cron address this directly with a noOverlap option, documented in the node-cron GitHub repository, which skips a scheduled run if the previous one hasn't finished.

Duplicate runs across multiple instances. Scale your app horizontally — two, three, ten instances behind a load balancer — and each instance runs its own independent in-process scheduler. Without coordination, every instance fires the same "daily report" job simultaneously. This is the core challenge of distributed cron in Node.js, and it's why node-cron's newer distributed-lock support exists, though it requires an external store (like Redis) to actually coordinate instances.

No built-in alerting, ever. Even a perfectly configured, non-overlapping, single-instance node-cron job has one universal weakness: if the callback throws an uncaught exception, or the process hangs before the log line executes, nothing tells you. The job just didn't run, silently, and your logs are the only record — if you're watching them.

How to Know When a Node.js Cron Job Actually Failed

The fix isn't a better scheduling library — it's an external heartbeat. The pattern is called ping-in/ping-out monitoring: your job pings a monitoring endpoint when it starts, and again when it finishes successfully. If the "finish" ping never arrives within an expected window, or the "start" ping never comes at all, the monitor knows something's wrong and alerts a human — even though your Node process itself was never aware of the problem.

This is exactly the layer Cronevra adds on top of any scheduler you're already using:

cron.schedule('0 */6 * * *', async () => {
  await fetch('https://cronevra.com/ping/your-job-id/start');
  try {
    await runSyncJob();
    await fetch('https://cronevra.com/ping/your-job-id/success');
  } catch (err) {
    await fetch('https://cronevra.com/ping/your-job-id/fail');
  }
});

Cronevra tracks execution history, flags missed or overdue runs, and sends recovery alerts — solving the exact blind spots node-cron, cron, node-schedule, and agenda were never built to cover. For a deeper look at reliability patterns beyond Node specifically, see Cron Jobs in Production: How to Keep Them Reliable.

Frequently Asked Questions

Is node-cron the same as Linux cron?

No. node-cron is a JavaScript library that runs inside a Node.js process and uses cron-style syntax for convenience, but it has no relationship to the system crontab. Linux cron runs independently of any application process; node-cron's schedule only exists while your Node app is running.

Can I use setInterval instead of a cron library in Node.js?

You can, but it lacks calendar-aware scheduling — setInterval only knows elapsed milliseconds, not "every day at 9am" or "first of the month." It also drifts over long uptimes and offers no cron-expression syntax, so most teams use setInterval only for simple, short, fixed-interval polling rather than real scheduling.

Why does my node-cron job stop running after a while?

The most common cause is the Node process itself crashing, restarting, or being redeployed, which wipes the in-memory schedule. Unhandled exceptions inside the job callback, or a process manager killing an unresponsive process, can also silently end scheduled execution.

How do I stop a node-cron job from running twice on multiple servers?

You need external coordination, since each app instance runs its own independent scheduler by default. node-cron's newer versions support distributed locking options documented on its GitHub page, typically backed by Redis, so only one instance executes a given scheduled run at a time.

Does node-cron survive a server restart or crash?

No. node-cron stores its schedule in memory only, so any process restart, crash, or redeploy clears all scheduled jobs until the app starts back up and re-registers them in code. There's no persistence layer or catch-up mechanism for runs missed during the downtime.

What's the difference between node-cron and the cron npm package?

They're separate libraries with similar names and overlapping cron-expression support, which causes frequent confusion. The cron package additionally supports scheduling by exact Date objects and offers slightly different job-control methods, while node-cron focuses more tightly on cron-expression scheduling with built-in overlap protection.

Your scheduler handles the timing — it was never designed to tell you when a run fails or never happens. Wrap your job with a start and success ping, and let Cronevra watch for the silence: check the pricing page to start monitoring your Node.js cron jobs free.