NodeJSThe cluster Module

The cluster Module

A single Node process runs your JavaScript on one thread and therefore one CPU core — so on an 8-core machine, a lone process leaves most of the hardware idle. The node:cluster module fixes this for network servers: it forks multiple worker processes that all share the same listening port, and the OS load-balances incoming connections across them. With one line of orchestration you go from using one core to using them all. This page covers how clustering works, a worker-per-core setup, the shared-port magic, restarting dead workers, zero-downtime reloads, and the state-sharing gotcha.

How clustering works

Text
              ┌──────────────────────────────────────┐
              │   Primary process (the "master")     │
              │   forks workers, doesn't serve traffic│
              └───────────────┬──────────────────────┘
                    forks      │
       ┌───────────────┬───────┼───────┬───────────────┐
       ▼               ▼               ▼               ▼
   ┌────────┐     ┌────────┐     ┌────────┐     ┌────────┐
   │Worker 1│     │Worker 2│     │Worker 3│     │Worker 4│   ← one per CPU core
   │ :3000  │     │ :3000  │     │ :3000  │     │ :3000  │   ← all share port 3000
   └────────┘     └────────┘     └────────┘     └────────┘
        ▲ OS/primary distributes incoming connections across workers ▲
The primary forks one worker per core; all workers share the listening socket and split the load
Clustering uses a **primary** process that forks **worker** processes — each a full copy of your app with its own memory and [event loop](/nodejs/event-loop). The trick is that the primary creates the listening socket and the workers share it, so all workers accept connections on the *same port*. Incoming connections are distributed across workers (round-robin by default on most platforms), giving you parallelism: with N workers on N cores, N requests can be handled truly simultaneously. The primary itself doesn't serve traffic — it supervises the workers, restarting them if they die.
A worker-per-core server

JS
import cluster from 'node:cluster'
import { availableParallelism } from 'node:os'
import http from 'node:http'

if (cluster.isPrimary) {
  const cpus = availableParallelism()   // number of logical cores
  console.log(`primary ${process.pid} forking ${cpus} workers`)

  for (let i = 0; i < cpus; i++) cluster.fork()

  // Restart any worker that dies so capacity self-heals:
  cluster.on('exit', (worker, code, signal) => {
    console.warn(`worker ${worker.process.pid} died (${signal || code}); restarting`)
    cluster.fork()
  })
} else {
  // Each worker runs the actual server — note they all listen on 3000:
  http.createServer((req, res) => {
    res.end(`handled by worker ${process.pid}\n`)
  }).listen(3000)
  console.log(`worker ${process.pid} listening on 3000`)
}
primary 4810 forking 8 workers
worker 4811 listening on 3000
worker 4812 listening on 3000
...
# curl repeatedly → different worker PIDs handle the requests
Fork one worker per core and respawn on `exit` — that gives both full CPU use and self-healing
The pattern is two-part. `availableParallelism()` reports the logical core count, and forking that many workers uses the whole machine. Then listening for the `exit` event and forking a replacement makes the cluster **self-healing**: if a worker crashes from an unhandled error or is OOM-killed, the primary spins up a fresh one and capacity recovers automatically — the rest of the workers keep serving throughout. This is a big resilience win over a single process, where one crash takes the whole server down until a process manager restarts it.
The shared-nothing state problem
Workers do NOT share memory — in-process state (sessions, caches, counters) breaks across workers
Each worker is a **separate process with its own memory**, so anything stored in-process is per-worker and not shared. This silently breaks common patterns under clustering: an in-memory **session store** means a user logged in on worker 1 appears logged out when the next request lands on worker 3; an in-memory **cache** is duplicated and inconsistent across workers; an in-memory **counter** or **rate-limiter** counts only its own worker's traffic. The fix is to move shared state to an **external store** — [Redis](/nodejs/redis) for sessions/cache/rate-limits, the database for durable data — so every worker reads and writes the same source of truth. Design your app stateless from the start and clustering (and horizontal scaling) just works.
Zero-downtime reloads

JS
// Restart workers ONE AT A TIME so the service never fully goes down (rolling reload):
function reloadWorkers() {
  const workers = Object.values(cluster.workers)
  let i = 0
  ;(function restartNext() {
    if (i >= workers.length) return
    const worker = workers[i++]
    const replacement = cluster.fork()
    replacement.once('listening', () => {
      worker.disconnect()             // stop sending it new connections
      worker.once('exit', restartNext) // when it has drained, do the next one
    })
  })()
}
process.on('SIGUSR2', reloadWorkers)   // trigger a rolling reload on signal
Restart workers one at a time and wait for each replacement to be ready — that's a zero-downtime deploy
Because the primary controls the worker pool, you can deploy new code with **no downtime**: replace workers one at a time, waiting for each new worker to be `listening` before `disconnect`ing the old one (which lets it finish in-flight requests before exiting). At every instant some workers are serving, so users never see an outage. This rolling-restart pattern — combined with [graceful shutdown](/nodejs/graceful-shutdown) inside each worker so it drains cleanly — is the foundation of seamless deploys. Process managers like PM2 implement this for you.
cluster vs alternatives

Approach

Pros

Cons

cluster module

Built-in, shared port, self-healing

You write the orchestration; single-machine only

PM2 / process manager

Cluster mode + reloads + monitoring, no code

External dependency

Container replicas

Scales across machines; cloud-native

Needs an external load balancer/orchestrator

Worker threads

Shared memory, lighter than processes

For CPU compute, not scaling HTTP across cores

In containers, you often run ONE process per container and scale replicas instead of clustering
Clustering is great on a single VM, but in **containerized** deployments the common pattern is the opposite: run a *single* Node process per container and scale by adding more container **replicas**, letting [Kubernetes](/nodejs/health-checks) or your orchestrator load-balance across them. This keeps each container simple (one process to monitor, clean resource limits) and scales beyond one machine, which `cluster` alone cannot. A [process manager](/nodejs/scaling-strategies) like PM2 gives you cluster mode plus reloads and monitoring without writing the orchestration yourself. Choose based on your deployment: `cluster`/PM2 for VMs, replicas for containers.
Next
True parallelism for CPU-bound work inside one process: [Worker Threads](/nodejs/worker-threads).