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
┌──────────────────────────────────────┐
│ 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 ▲A worker-per-core server
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
The shared-nothing state problem
Zero-downtime reloads
// 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 signalcluster vs alternatives
Approach | Pros | Cons |
|---|---|---|
| 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 |