# node-cron: in-process cron scheduling for Node.js with overlap and multi-instance control

> node-cron schedules recurring work inside a Node.js process, adds noOverlap and distributed guards, and can fork heavy jobs into separate processes. It is not a durable queue, and the repository is silent on several operational details.

**node-cron/node-cron** — Job scheduling for Node.js with overlap prevention, distributed coordination, and background tasks. Zero dependencies.

- Repository: https://github.com/node-cron/node-cron
- Website: https://nodecron.com
- Stars: 3,280 · Forks: 283
- Language: TypeScript
- License: ISC
- Published: 2026-09-24 · Updated: 2026-09-24 · Language: en
- Canonical page: https://hysenlabs.com/projects/node-cron-node-cron

## What node-cron solves, and for whom

The package targets a narrow but common shape of work: something has to happen on a wall-clock schedule, and that something lives inside a Node.js process you already run. The README frames the scope as recurring jobs on cron expressions with second-level precision, overlap prevention for long-running tasks, coordination across instances or replicas, and heavy jobs in isolated background processes. That list is the honest boundary. There is no worker fleet to deploy, no separate scheduler daemon, and no database table of job state. If your application already boots a Node process, you can attach a schedule to it.

The audience is therefore backend engineers running a service, not data engineers orchestrating a pipeline graph. A nightly backup, a cache warm-up every five minutes, a report that fires at 03:00 in a specific timezone: these fit. A multi-step workflow where step three must run only if step two succeeded and must be retried with backoff does not, and nothing in the README claims otherwise.

## The scheduling mechanism: one process, a cron parser, and optional guards

The core API is a single function. The README's first example passes a five-field expression and a callback, and the callback runs every minute. Tasks are objects, and every task exposes the same control surface: stop, start, destroy, getStatus, getNextRun and lastRun. Status is one of four strings, stopped, idle, running or destroyed, and lastRun returns an object carrying either a result or an error.

Two options change the execution model rather than the schedule. With noOverlap, a tick that arrives while the previous run is still active is skipped instead of stacked. With distributed, only one instance executes each fire; the README says that out of the box this uses an env-var flag, and that a Redis coordinator can be plugged in for high availability. That default is worth pausing on. An env-var flag is a per-process switch, not a coordination protocol, so the README's own wording points you at Redis when you actually need agreement between replicas.

The third option is structural. Passing a file path instead of a function forks the job into an isolated process, which is how you keep CPU-heavy work off the event loop. The README warns that the fork resolves a helper relative to node-cron's own files inside node_modules, so bundlers must treat the package as external or the fork fails with a missing daemon.js module. Inline function tasks are unaffected by that constraint.

## Installing node-cron and running a first real task

The package requires Node 20 or newer, per the engines field in package.json, and ships both ESM and CommonJS entry points. Install it from npm:

```bash
npm install node-cron
```

The README's minimal example imports the default export and schedules a callback. Run it and you should see the log line once per minute:

```javascript
import cron from 'node-cron';

cron.schedule('* * * * *', () => {
  console.log('running a task every minute');
});
```

For a job you will actually operate, name it, set a timezone, and turn on overlap skipping. The README shows this shape for a nightly backup, and the returned task object is what you inspect later:

```javascript
const task = cron.schedule('0 3 * * *', runNightlyBackup, {
  name: 'nightly-backup',
  timezone: 'America/Sao_Paulo',
  noOverlap: true,
});
```

From there, task.getNextRun() returns the next scheduled Date or null, and task.lastRun() returns the previous outcome. Subscribing to execution:failed and execution:overlap is the cheapest way to see whether the job is doing what you think. Note that the README documents the option keys as shown; it does not publish a full option reference in the excerpt above, so confirm the installed version's types before copying these into a larger configuration.

## Cron syntax, including the Quartz borrowings that are not Quartz

node-cron accepts the standard five fields plus an optional leading second field, so second-level precision is available when you want it. Ranges, steps and lists work as expected, and month and weekday names are accepted. Beyond that, the parser borrows several modifiers from Quartz: L for the last day of the month, L-n for an offset from it, W for the nearest weekday, LW for the last weekday of the month, # for the nth weekday, weekdayL for the last given weekday, and ? as an alias for * in the day fields.

The README is unusually direct that this is not Quartz compatibility. Two differences matter in practice. Day-of-week numbering follows standard cron, so 0 and 7 are Sunday and 1 is Monday; in Quartz, 1 is Sunday, which means the same numeric expression fires on a different day. And day-of-month and day-of-week are combined with AND, so both must match, while Quartz treats them as mutually exclusive and requires ? in one of them. The ? token is accepted so that Quartz-style strings parse, not as a promise that they mean the same thing.

Two other behaviours are documented and easy to miss. An inverted range wraps instead of being rejected, so 22-2 in the hour field covers 22:00 through 02:59. And W adjusts only for weekends: the README states plainly that there is no holiday awareness, so a 15W job will fire on a public holiday if that holiday falls on a weekday.

## Where node-cron stops being the right tool

The clearest limitation is durability. Nothing in the README describes persisting schedule state across restarts, recovering missed fires after a crash, or retrying a failed execution. There is an execution:missed event and an execution:maxReached event in the documented list, which tells you the library notices these conditions, but the README does not describe a recovery policy for either. If a process is down at 03:00, the README gives no indication that the 03:00 job runs later.

That makes node-cron a poor fit for work where a missed run is an incident: billing runs, exactly-once payment capture, anything that must survive a deploy window. It is also a poor fit when the schedule must be changed by someone who cannot deploy code, because schedules are defined in the process rather than in shared storage.

The distributed option deserves its own caution. The README says the default mechanism is an env-var flag and that a Redis coordinator is the high-availability path. An env-var flag cannot arbitrate between replicas that do not share it, so the safe reading is that the default is a single-instance convenience and that multi-replica correctness depends on configuring the coordinator. The README does not document what happens when the coordinator is unreachable, so that failure mode is unverified here.

## node-cron against node-schedule and Agenda

node-schedule is the closest comparison and the one people search for most. The API difference is visible in the names: node-schedule centres on scheduling a Date or a recurrence rule and offers job cancellation, while node-cron centres on cron expression strings and adds two things node-schedule's documentation does not present as core features, overlap prevention via noOverlap and multi-instance coordination via distributed. If your requirement is a one-off run at a specific timestamp, node-schedule's model is a more natural fit; if it is a recurring expression with a guard against stacked runs, node-cron's options are built for that.

Agenda takes a different architectural position altogether: it is backed by MongoDB, so job definitions and state live in the database rather than in the process. The trade is explicit. Agenda gives you persistence and a shared view of jobs across workers, at the cost of a MongoDB dependency and a database round trip in the scheduling path. node-cron's package.json lists zero runtime dependencies, which is the opposite end of that spectrum. Neither is better in the abstract; the question is whether losing in-process state on restart is acceptable for the job in front of you.

## Maintenance status, licence and upgrade cost

The repository is not archived, and the last push was on 2026-09-11, which is recent enough that describing it as actively maintained is defensible on the evidence available. Releases are frequent and small: v4.6.0 on 2026-07-05, v4.5.0 on 2026-06-21, and v4.4.1 on 2026-06-18. The presence of release-please-config.json and .release-please-manifest.json in the repository root indicates automated release tooling, which is consistent with that cadence.

The licence is ISC, a permissive licence that imposes minimal obligations; the repository carries LICENSE.md. That is a statement about what the licence is, not legal advice, and anyone redistributing the package should read the file rather than this summary.

Upgrade cost is dominated by the Node 20 floor in the engines field. If you run an older runtime, you cannot take v4 without a runtime upgrade first, and that is a larger project than the package change. Within v4, the API surface documented in the README is small and stable-looking: one schedule function, a task object with six methods, and an event list. The bundler constraint on file-path tasks is the one upgrade hazard that will not show up in type checking, because it fails at fork time rather than at compile time.

## Conclusion

Adopt node-cron when scheduled work belongs to a single Node.js service and you want cron syntax, overlap skipping and a forked-process escape hatch without adding a dependency. Do not adopt it as a replacement for a durable job queue or a cluster-wide scheduler; the README does not document persisted state, retry policies or rollback. Before writing production jobs, verify the installed version's exported option keys against the README, confirm your bundler marks node-cron external if you use file-path tasks, and check that a Redis coordinator is actually configured before you run more than one replica.

## FAQ

### How do I install node-cron?

Install it from npm with npm install node-cron. The package requires Node 20 or newer according to its engines field, and it ships both ESM and CommonJS builds.

### How do I use node-cron?

Import the default export and call cron.schedule with a cron expression and a callback. The call returns a task object with stop, start, destroy, getStatus, getNextRun and lastRun methods, and options such as name, timezone and noOverlap can be passed as a third argument.

### What is node-cron used for?

It runs recurring jobs on a cron schedule inside a Node.js process, with second-level precision. The README also lists overlap prevention for long-running tasks, coordination across instances, and running heavy jobs in isolated background processes.

### Is node-cron reliable?

The README documents overlap skipping, a distributed mode and lifecycle events including execution:failed and execution:missed, but it does not describe persisting schedule state across restarts or a retry policy for failed or missed runs. Reliability therefore depends on what the surrounding process does about those events.

### Is node-cron free?

Yes. The package is published under the ISC licence and the repository includes a LICENSE.md file.

### What does node-cron do?

It schedules recurring tasks in a Node.js process using cron expressions, with an optional leading second field. The README also documents noOverlap for skipping stacked runs, distributed for coordinating across instances, and file-path jobs that run in a forked process.

## Sources

- [License: ISC](https://github.com/node-cron/node-cron/blob/main/LICENSE)
- [node-cron/node-cron on GitHub](https://github.com/node-cron/node-cron)
- [Project website](https://nodecron.com)
- [README](https://github.com/node-cron/node-cron/blob/main/README.md)
- [Releases](https://github.com/node-cron/node-cron/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/node-cron-node-cron
