The Node.js cluster module lets a single Node.js process fork itself into multiple worker processes, all sharing the same server port, to utilize all available CPU cores.

Here’s the cluster module in action, serving requests across multiple cores:

const cluster = require('cluster');
const http = require('http');
const numCPUs = require('os').cpus().length;

if (cluster.isMaster) {
  console.log(`Master ${process.pid} is running`);

  // Fork workers.
  for (let i = 0; i < numCPUs; i++) {
    cluster.fork();
  }

  cluster.on('exit', (worker, code, signal) => {
    console.log(`worker ${worker.process.pid} died`);
    // Optionally, re-fork a new worker if one dies
    if (code !== 0 && !worker.suicide) {
      console.log('Forking a new worker...');
      cluster.fork();
    }
  });
} else {
  // Workers can share any TCP connection
  // In this case it is an HTTP server
  http.createServer((req, res) => {
    res.writeHead(200);
    res.end(`Hello from worker ${process.pid}\n`);
  }).listen(8000);

  console.log(`Worker ${process.pid} started`);
}

When you run this script, you’ll see output like:

Master 12345 is running
Worker 12346 started
Worker 12347 started
Worker 12348 started
Worker 12349 started

And if you hit http://localhost:8000 in your browser or with curl, you’ll get responses like:

Hello from worker 12346

or

Hello from worker 12348

The master process, identified by cluster.isMaster === true, is responsible for forking worker processes. These workers are the ones that actually handle incoming requests. The magic is that they all bind to the same port (8000 in this example). Node.js’s underlying operating system handles distributing incoming connections across these listening workers. This means that each CPU core can potentially handle a portion of the incoming traffic, dramatically increasing your application’s throughput for CPU-bound tasks.

The cluster.on('exit', ...) event is crucial for resilience. If a worker process crashes or is terminated, the master process can detect this and spawn a new worker to replace it, ensuring your application remains available.

The primary problem this solves is Node.js’s single-threaded nature. While Node.js excels at I/O-bound concurrency through its event loop, CPU-bound operations can block that single thread, halting all other operations. The cluster module bypasses this limitation by creating independent processes. Each worker has its own V8 engine and event loop, allowing them to execute CPU-intensive code in parallel without blocking each other.

The cluster.fork() method creates a new worker process. Each worker inherits environment variables and inherits file descriptors from the master. This means they can all connect to the same database or listen on the same port without conflict. The cluster.isMaster (or cluster.isPrimary in newer Node.js versions) boolean is how you differentiate the master process from the worker processes within the same script.

Communication between master and workers, or between workers themselves, can be achieved using process.send() and worker.on('message', ...). This is useful for scenarios where workers need to signal status to the master, or for more complex coordination. For example, a master might want to send a list of tasks to all workers, or a worker might report its current load.

When a worker process dies, the code and signal arguments in the exit event handler provide information about why it exited. A code of 0 typically means a graceful shutdown, while non-zero codes indicate an error. worker.suicide is a boolean that’s true if the worker was intentionally shut down by the master (e.g., during a graceful restart).

One subtle but powerful aspect is how the operating system load balances connections. When multiple workers listen on the same port, the OS’s network stack decides which worker gets the next incoming connection. This is typically a round-robin or least-connections algorithm, effectively distributing the load automatically. You don’t need to implement your own load balancer at the Node.js level for basic port sharing.

The next step after mastering basic clustering is understanding how to gracefully restart workers without dropping connections, often involving process.send('graceful-shutdown') and worker.disconnect().

Want structured learning?

Take the full Nodejs course →