Zero-Downtime Reloads with PM2 Cluster Mode: Signals, Timeouts, and the wait_ready Flag
Learn how PM2's master process orchestrates graceful worker replacement, the role of SIGINT/SIGKILL, and why wait_ready matters for reliable deployments.
04 Aug 2026, 12:04 UTC

The problem: deployments that drop in-flight requests
When a Node.js service restarts, the operating system closes the listening socket. Any request that arrives during the restart window gets a connection-refused error. PM2’s cluster mode solves this by keeping a master process alive and replacing workers one at a time, but only if the application cooperates with the signals PM2 sends.
How PM2 orchestrates a graceful reload
In cluster mode, PM2 uses Node’s built-in cluster module. The master holds the TCP port and forks N workers (where N is the instances value in the ecosystem file). A reload proceeds as follows:
- The master picks a worker and sends a
SIGINTsignal. - The worker has a grace period (default 1.6s, configurable via
kill_timeout) to finish current work, close its HTTP server, and exit. - If the worker is still alive after the grace period, the master sends
SIGKILLto force termination. - The master spawns a fresh worker. When
wait_readyis enabled, the new worker must callprocess.send('ready')before PM2 marks it healthy and routes traffic to it. - These steps repeat for each worker until the entire cluster is updated.
Because the master never releases the port, new connections are always accepted by at least one healthy worker.
Key configuration knobs
| Option | Purpose | Typical Value |
|---|---|---|
exec_mode: 'cluster' | Enables multi-worker mode | Required for zero-downtime |
instances: 'max' | Fork one worker per CPU core | 'max' or a number |
wait_ready: true | Wait for explicit ready message | true |
listen_timeout: 5000 | Max ms to wait for ready before kill | 5000–10000 |
kill_timeout: 3000 | Grace period after SIGINT before SIGKILL | 3000–10000 |
max_memory_restart: '200M' | Restart worker when heap exceeds limit | Optional |
These settings are defined in the ecosystem.config.js file to ensure behavior is reproducible across environments.
Worked example: Express app that signals readiness
// ecosystem.config.js
module.exports = {
apps: [{
name: 'api',
script: 'server.js',
exec_mode: 'cluster',
instances: 'max',
wait_ready: true,
listen_timeout: 8000,
kill_timeout: 5000,
env: { NODE_ENV: 'production' }
}]
};
// server.js
const express = require('express');
const app = express();
app.get('/health', (req, res) => res.send('ok'));
const server = app.listen(process.env.PORT || 3000, () => {
console.log(`Worker ${process.pid} listening`);
// Tell PM2 this worker is ready to receive traffic
if (process.send) process.send('ready');
});
// Graceful shutdown on SIGINT
process.on('SIGINT', () => {
console.log(`Worker ${process.pid} received SIGINT`);
server.close(() => {
console.log('HTTP server closed');
process.exit(0);
});
// Force exit if close takes too long
setTimeout(() => process.exit(1), 4000);
});
To deploy this configuration, run the following on the target host (requires global PM2 installation):
pm2 start ecosystem.config.js
Trigger a zero-downtime reload after updating your code:
pm2 reload api --update-env
Verify the transition by watching the logs:
pm2 logs api --lines 50
You should see each worker log received SIGINT, then HTTP server closed, followed by a new worker PID printing listening and sending ready.
Trade-offs and limitations
- Application Signal Handling: If your code ignores
SIGINT, in-flight requests are dropped when the worker is eventually killed bySIGKILL. - Sticky Sessions and WebSockets: PM2 does not migrate live sockets between workers. Use an external load balancer with sticky routing or the
pm2-websocketmodule for WebSocket affinity. - wait_ready Deadlock: If the application fails to send
ready(e.g., due to a startup crash), the reload hangs untillisten_timeoutexpires, which may cause a brief outage. - Native Addons: Some C++ addons are not fork-safe; each worker loads its own copy, which increases memory usage and can break global state.
- Master SPOF: The PM2 master is a single point of failure. If it crashes, workers become orphaned. Use
pm2 startupto generate a systemd/launchd unit to ensure the master restarts automatically.
Actionable closing checklist
- Implement
process.on('SIGINT', ...)andprocess.send('ready')in your entry point. - Set
wait_ready: trueand a sensiblelisten_timeoutinecosystem.config.js. - Run
pm2 reload <app> --update-envand verify viapm2 logsthat workers exit cleanly. - Confirm cluster mode and timeouts are active using
pm2 show <app>. - Execute
pm2 startupand check the generated unit (e.g.,systemctl cat pm2-user) to ensure master resilience.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.