A single Node.js process uses a single CPU core. Your server has eight. Out of the box, seven of them sit idle while one does all the work — which means a default Node deployment leaves most of the machine you are paying for unused. The cluster module is how you fix that: it runs multiple Node processes that share the same port, so all your cores serve requests. Understanding it, and where it sits relative to worker threads and horizontal scaling, is what lets you actually use the hardware you have.
This is part of the Node.js runtime series. Here we cover clustering, why it works, and how it fits into a broader scaling strategy.
Why one process is not enough
The event loop is single-threaded, and that single thread runs on one core. This is fine for the I/O concurrency Node excels at — one thread juggles thousands of connections happily — but it means a single Node process can never use more than one core's worth of CPU. On a multi-core machine, that is a lot of wasted capacity, and it caps how much a single process can handle before the one core it uses is saturated.
The cluster module solves this by running several Node processes instead of one. You start a master process that forks multiple worker processes — typically one per CPU core — and they all listen on the same port. Incoming connections are distributed across the workers, so requests are handled in parallel across all your cores. Eight cores, eight workers, eight requests being processed at once instead of one.
const cluster = require("node:cluster")
const os = require("node:os")
if (cluster.isPrimary) {
for (let i = 0; i < os.availableParallelism(); i++) {
cluster.fork() // one worker per core
}
cluster.on("exit", (worker) => {
cluster.fork() // replace a worker that died
})
} else {
startServer() // each worker runs the app
}
The operating system and Node handle distributing connections across the workers, so from your application's perspective each worker is just your normal server, running on its own core.
Processes, not threads
A key distinction from worker threads: cluster workers are separate processes, not threads. Each has its own memory, its own event loop, its own everything — they share nothing except the listening port. This is exactly why clustering works cleanly for scaling a web server: since the workers share no memory, there is no coordination between them at the application level, and one crashing does not corrupt the others. The master can simply fork a replacement, as in the exit handler above, giving you a basic resilience win alongside the throughput one.
It also means clustering and worker threads solve different problems and can be used together. Clustering multiplies your request-handling capacity across cores. Worker threads offload CPU-bound tasks off the main thread within a single process. A service might cluster across eight cores for throughput, and each worker might use a worker thread for the occasional heavy computation. They are complementary, not alternatives.
The catch: workers share no state
Because cluster workers have separate memory, anything you stored in process memory is per-worker, and this breaks two common patterns the moment you cluster.
The first is in-memory state. If you cache data in a module-level variable, or keep user sessions in memory, each worker has its own copy, and a user whose requests land on different workers sees inconsistent data — logged in on one, logged out on the next. The fix is the same statelessness that horizontal scaling demands: push shared state out of process memory into something all workers reach, like Redis. Sessions, caches, and rate-limit counters all belong in a shared store, not a local variable, the moment you run more than one process.
The second is anything that must happen exactly once. Scheduled jobs are the classic trap — if every worker runs the same cron task, a job meant to fire once now fires eight times, once per worker. You have to elect a single worker (or move the scheduler out of the clustered process entirely) so the job runs once. These are not clustering bugs; they are the natural consequence of going from one process to many, and they are exactly the constraints you also hit when scaling to multiple machines.
Clustering versus running multiple containers
Here is a question worth answering honestly, because the modern deployment landscape changes the calculus. The cluster module scales you across the cores of one machine. But if you deploy on a platform that runs your app in containers and scales by adding more container instances — which most cloud and container platforms do — then the platform is already giving you multiple processes across cores, and adding the cluster module inside each container can be redundant or even counterproductive.
The general guidance: if you self-host on a big multi-core box and run the process directly, the cluster module is how you use all the cores, and it is the right tool. If you deploy in containers behind an orchestrator that scales instances for you, prefer running one process per container and letting the platform scale the instance count — it is simpler, and the orchestrator handles the distribution, health checks, and replacement that you would otherwise be reimplementing with cluster. Either way the goal is identical: use every core, keep each process stateless, and let something distribute the load. Whether that "something" is the cluster master or a container orchestrator is a deployment choice.
Where clustering fits in scaling
Clustering is the bridge between a single process and true horizontal scaling. It gets you from one core to all the cores on one machine — vertical scaling of your process count, in effect. Beyond that, when one machine is not enough, you scale out to multiple machines behind a load balancer, and the statelessness you were forced into by clustering is exactly what makes that next step easy: a stateless app that runs correctly across eight cluster workers already runs correctly across eight machines. The disciplines compound. Get the app stateless for clustering and you have done most of the work for scaling it as far as you will ever need.
Originally published at amanksingh.com.
Top comments (0)