Filling Every Core with PM2 Cluster Mode and Ecosystem Files
PM2 cluster mode uses every CPU core and supports zero-downtime reloads, but only if your app is stateless and handles SIGINT. Here is how to configure it with an ecosystem file and verify it works.
19 Oct 2025, 12:45 UTC

Your Node.js server is leaving CPU cores idle, and a hard restart drops every active connection. PM2 cluster mode fixes the first problem when you declare workers in an ecosystem file, and pm2 reload addresses the second—provided your application handles shutdown signals properly. The catch is that cluster mode assumes your app is stateless; in-memory sessions or local caches will not survive a worker restart.
Why one Node.js process leaves cores idle
Node.js runs your JavaScript on a single thread per process. On a host with multiple cores, a default node server.js or pm2 start server.js in fork mode keeps one core busy while the others sit nearly unused. Under load, that single event loop becomes the bottleneck long before the CPU is saturated.
PM2 cluster mode uses the Node.js built-in cluster module to fork multiple worker processes from a single parent. Each worker runs the same script and listens on the same port; the cluster module distributes incoming connections across them. The result is that one application instance can occupy every core on the machine without you rewriting the app to spawn threads.
Declaring workers in an ecosystem file
Passing flags on the command line works for experiments, but it does not scale well. An ecosystem file—typically ecosystem.config.js—lets you declare the process shape in version control. You set the number of instances, the execution mode, environment variables, and restart policies in one place.
Here is a minimal production-oriented configuration:
// ecosystem.config.js
module.exports = {
apps: [{
name: "api",
script: "./server.js",
instances: "max",
exec_mode: "cluster",
max_memory_restart: "500M",
env: {
NODE_ENV: "production",
PORT: 3000
}
}]
};
Run this from your project root with pm2 start ecosystem.config.js. The instances: "max" setting tells PM2 to spawn one worker per available CPU core. If you prefer a fixed number, replace "max" with an integer. The exec_mode: "cluster" line is what activates the multi-core behavior; without it, PM2 defaults to fork mode and you get a single process. If your app binds to a privileged port below 1024, the PM2 daemon needs elevated permissions or a capability such as cap_net_bind_service; otherwise run as a standard user.
After starting, verify the state with pm2 list. You should see one row per worker, all with status online, and the instance count should match your core count. To confirm the mode and memory ceiling, run pm2 describe api (replace api with your app name) and check that exec mode reports cluster_mode and max_memory_restart reflects your configured value.
Reloading without dropping the port
A standard pm2 restart kills workers and starts new ones, which means a window where the port may be unbound. pm2 reload is different: it cycles workers one at a time. PM2 starts a replacement worker, waits for it to bind, then gracefully stops the old one, and repeats until every worker has been replaced. The listening socket stays open throughout.
To deploy new code, run pm2 reload ecosystem.config.js from the same project root. During the operation, watch pm2 logs api. You should see workers restart sequentially rather than all at once. Afterward, compare process IDs in pm2 list; they should have changed while the port remains responsive.
The max_memory_restart value adds a safety net for long-running processes. If a worker exceeds the ceiling—say, because of a slow memory leak—PM2 restarts that individual worker automatically. This contains the blast radius: one bloated worker is recycled without taking down the whole cluster.
The stateless catch and other trade-offs
Cluster mode is not free. Because workers are separate processes, they do not share memory. If your application stores session data in a local JavaScript object, keeps an in-memory cache, or relies on WebSocket connections pinned to a specific worker, a reload or memory-triggered restart will lose that state. You need an external store such as Redis for sessions and cache, or you must accept that local state is ephemeral.
Zero-downtime reloads also depend on graceful shutdown handling. When PM2 sends SIGINT to a worker, your application should stop accepting new connections, finish in-flight requests, and then exit. If your code ignores the signal or has a long-running request that never completes, that request can still be dropped when the worker is terminated. Test this by firing a slow request and triggering a reload.
PM2 cluster mode balances load within one machine only. If you need to scale beyond a single host, you still need an external load balancer in front of multiple PM2-managed servers. And log rotation is not enabled by default; without the pm2-logrotate module, log files can grow until the disk fills. Install it with pm2 install pm2-logrotate if you have not already.
A practical way to check your setup
Do not assume the cluster is healthy because the app responds. Run pm2 list and confirm every worker is online. Run pm2 describe api and confirm exec mode is cluster_mode. Trigger a reload with pm2 reload ecosystem.config.js and watch pm2 logs to ensure workers cycle one by one. If any of those checks fail, the configuration—not the application code—is the first place to look.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.