Use Every Core With Node Cluster (or PM2)
Clustering & Multi-Core
Node runs JS on a single thread. Spawn one process per core to saturate the CPU and serve more requests per second.
What you'll learn
- Use node cluster or PM2 cluster mode
- Share a port across forked workers
- Handle SIGTERM gracefully in every worker
A single Node process uses one CPU core. On a four-core box that leaves 75% of your CPU on the table. The fix is to run one Node process per core; the OS routes connections to whichever worker is free.
Option 1: node:cluster
import cluster from 'node:cluster'
import os from 'node:os'
if (cluster.isPrimary) {
const workers = Number(process.env.WEB_CONCURRENCY ?? os.availableParallelism())
for (let i = 0; i < workers; i++) cluster.fork()
cluster.on('exit', () => cluster.fork())
} else {
const { start } = await import('./server.js')
await start()
} The primary forks workers and replaces any that crash. Workers share the listening port, so clients only see a single endpoint.
Option 2: PM2
PM2 wraps the same idea with a process manager and a dashboard.
npm install -g pm2
pm2 start dist/index.js -i max --name web
pm2 logs web
pm2 reload web pm2 reload does a rolling restart — handy for zero-downtime deploys.
Graceful Shutdown In Every Worker
Each worker must handle SIGTERM so reloads are clean:
const app = buildApp()
await app.listen({ port: 3000, host: '0.0.0.0' })
for (const sig of ['SIGTERM', 'SIGINT'] as const) {
process.once(sig, async () => {
app.log.info({ sig }, 'shutting down')
await app.close()
process.exit(0)
})
} If you serve WebSockets, configure your load balancer for sticky sessions — otherwise the upgrade and the subsequent traffic might land on different workers.
Microservices With Fastify →