Friday 5 p.m., urgent deploy: pm2 restart api. For thirty seconds, the external probe reports 502s, support is flooded, and a partner retries webhooks in a loop. Monday morning, someone tries pm2 reload api in cluster mode: same version, same code — and the probe records no interruption.
PM2 is not just a daemon "so Node runs." It is a process orchestrator with specific restart semantics. Confusing restart and reload in production means choosing a micro-outage on every release.
ecosystem.config.js: the foundation of a clean deploy
A versioned ecosystem.config.js avoids ad hoc commands forgotten at the next handover. Here is a sensible starting point for a Node.js API in production:
module.exports = {
apps: [{
name: 'api',
script: './dist/server.js',
instances: 'max',
exec_mode: 'cluster',
wait_ready: true,
listen_timeout: 10000,
kill_timeout: 5000,
max_memory_restart: '600M',
env_production: {
NODE_ENV: 'production',
PORT: 3000,
},
}],
};
If you enable wait_ready: true, the app must signal readiness with process.send('ready') once the HTTP server can accept traffic. kill_timeout sets how long workers have to finish requests before a forced stop.
A
kill_timeoutthat is too short turns a graceful reload into a disguised restart.
Graceful shutdown: what PM2 will not do for you
PM2 can wait — but only if your code knows how to close cleanly. Without SIGINT or SIGTERM handlers, reload and restart look the same: connections cut mid-request.
process.on('SIGINT', () => {
server.close(() => process.exit(0));
setTimeout(() => process.exit(1), 8000).unref();
});
Also close the database pool, flush log buffers, and stop queue consumers. For WebSockets, either notify clients to reconnect or drain active connections before shutdown.
Recommended deploy workflow
A no-downtime deploy usually follows this sequence:
- Fetch the release (Git pull or atomic symlink swap).
- Install dependencies with
npm ci --production. - Run
pm2 reload ecosystem.config.js --env production --update-env. - Save the config with
pm2 saveso it survives a server reboot. - Run a smoke test on the health endpoint.
To roll back, repoint the symlink to the previous artifact and reload again. Avoid pm2 delete followed by start except on first install — you would lose metrics and restart history.
nginx in front of PM2: split the roles
In production, nginx terminates TLS and forwards dynamic traffic to PM2:
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
Align nginx timeouts (proxy_read_timeout, proxy_connect_timeout) with PM2's kill_timeout. For WebSockets, see our guide on WebSocket realtime.
On a VPS, configure pm2 startup systemd with a non-root user. Size RAM from worker count and peak memory per instance. On Heroku-like PaaS, the platform often manages process lifecycle; PM2 matters most on self-hosted VPS or bare metal.
The peak: PM2 hides missing graceful shutdown — until reload
Before every production merge, test SIGINT and SIGTERM locally, then run a reload on staging. That is the gap between an invisible release and thirty seconds of 502s your customers measure.
Decide and move forward without blind spots
In half a day you can harden Node.js deploys:
- Switch to cluster mode if your HTTP API is stateless, and replace
restartwithreloadin every deploy script. - Implement and test SIGINT/SIGTERM handlers — server close, database pool, queue consumers.
- Version a complete
ecosystem.config.jswith production env vars and memory limits. - Put nginx in front of PM2 for TLS, static files, and rate limiting.
- Monitor restart count and event-loop lag to catch memory leaks before they trigger automatic restarts.
For memory leaks in Node.js, see Node memory leak. To compare hosts suited to VPS deploys, browse our directory.
Frequently asked questions
restart vs reload PM2?
restart kills and starts again — guaranteed outage. reload sends graceful shutdown, waits for in-flight requests, then relaunches workers one by one in cluster mode. In production, reload is the default choice.
When use cluster mode?
When a stateless HTTP app should use multiple CPU cores. Cap at one worker per core. For WebSockets, add sticky sessions or a shared Redis adapter across workers.
PM2 without nginx?
Possible, but nginx is recommended for TLS, static files, and abuse protection. PM2 listens on the app port; nginx is the public reverse proxy.
Env vars on deploy?
Centralize in a versioned ecosystem.config.js and use --update-env on reload. Keep secrets out of Git — server file or dedicated vault.
PM2 in production means reload with graceful shutdown — not restart while users are still connected.