Clustering & Using Every Core
Node runs on one core; cluster (or your platform's replicas) fan out across all of them — but only if your app is stateless.
A single Node process uses a single CPU core. On an 8-core box that is 12% of your hardware. The node:cluster module forks one worker per core, all sharing the same listening port, and the OS load-balances connections across them.
The prerequisite nobody mentions
Clustering only works if your app is stateless. The moment you keep sessions in an in-memory store, or an in-process cache, each worker has its own copy and users bounce between inconsistent views. Externalize all state (Redis, the database) before you scale out — this is the same discipline that lets you run multiple containers.
import cluster from 'node:cluster';
import { availableParallelism } from 'node:os';
if (cluster.isPrimary) {
for (let i = 0; i < availableParallelism(); i++) cluster.fork();
} else {
startServer();
}Cluster vs the platform
Under Kubernetes or a PaaS, prefer one process per container and let the orchestrator run N replicas — it already handles restarts, health and scaling. Reach for cluster (or pm2 -i max) on a single VM you manage yourself.
Example
import cluster from 'node:cluster';
import { availableParallelism } from 'node:os';
import process from 'node:process';
if (cluster.isPrimary) {
const workers = availableParallelism();
console.log(`Primary ${process.pid} forking ${workers} workers`);
for (let i = 0; i < workers; i++) cluster.fork();
// Keep the pool healthy: replace any worker that dies.
cluster.on('exit', (worker, code, signal) => {
console.warn(`Worker ${worker.process.pid} died (${signal || code}); restarting`);
if (!worker.exitedAfterDisconnect) cluster.fork();
});
} else {
// Each worker is a normal, stateless Express app sharing the port.
const { default: app } = await import('./app.js');
const server = app.listen(process.env.PORT ?? 3000, () => {
console.log(`Worker ${process.pid} listening`);
});
// Workers must honor graceful shutdown too.
process.on('SIGTERM', () => server.close(() => process.exit(0)));
}When to use it
- A team uses the Node.js cluster module to fork one worker per CPU core so the Express server fully utilises a 16-core production machine.
- A developer uses PM2 in cluster mode to auto-restart dead workers and provide zero-downtime deploys without touching application code.
- A load-balanced Express cluster uses sticky sessions so WebSocket upgrade requests always reach the same worker that holds the socket state.
More examples
Node.js cluster module
The primary process forks one worker per CPU core and auto-restarts any worker that crashes.
const cluster = require('cluster');
const os = require('os');
const app = require('./app');
if (cluster.isPrimary) {
const cpus = os.cpus().length;
console.log(`Primary forking ${cpus} workers`);
for (let i = 0; i < cpus; i++) cluster.fork();
cluster.on('exit', (worker) => {
console.log(`Worker ${worker.process.pid} died, respawning`);
cluster.fork();
});
} else {
app.listen(process.env.PORT || 3000);
console.log(`Worker ${process.pid} started`);
}PM2 cluster mode config
Configures PM2 to run the server in cluster mode across all CPU cores with a memory limit that triggers automatic worker restart.
// ecosystem.config.js
module.exports = {
apps: [{
name: 'my-api',
script: 'server.js',
instances: 'max', // one per CPU
exec_mode: 'cluster',
watch: false,
max_memory_restart: '500M',
env_production: {
NODE_ENV: 'production',
PORT: 3000,
},
}],
};Zero-downtime reload with PM2
Uses pm2 reload (not restart) to roll the updated code across workers one at a time, maintaining availability during deploys.
# Start in cluster mode
pm2 start ecosystem.config.js --env production
# Deploy a new version with zero-downtime rolling reload
pm2 reload my-api
# Monitor all workers
pm2 monit
Discussion