Node.js is single-threaded; a lone process uses a single CPU core. On an 8-core box you leave 87% of the hardware idle. Cluster mode plus PM2 gives you every core and zero-downtime deploys. This guide walks through every step of a production setup.
What Is Cluster Mode?
Node.js's cluster module spawns multiple workers under a single master process. Workers share the same port, and the kernel distributes incoming connections between them. 8 cores means 8 workers means roughly 8x throughput.
Installing PM2 and Basic Usage
# Global install
npm i -g pm2
# Start a cluster with one command
pm2 start server.js -i max --name myapp
# -i max = one worker per core
# Status
pm2 list
pm2 monit # live dashboard
pm2 logs myapp --lines 100
pm2 describe myapp
# Operations
pm2 restart myapp
pm2 reload myapp # zero-downtime — rolling worker restart
pm2 stop myapp
pm2 delete myapp
ecosystem.config.js
In production, use a config file instead of inline flags. Every setting lives in version control.
// ecosystem.config.js
module.exports = {
apps: [{
name: 'keydal',
script: './server.js',
instances: 'max', // every core
exec_mode: 'cluster',
// Restart if memory exceeds this
max_memory_restart: '512M',
// Env
env: {
NODE_ENV: 'development',
PORT: 3000
},
env_production: {
NODE_ENV: 'production',
PORT: 3000
},
// Logs
out_file: '/var/log/keydal/out.log',
error_file: '/var/log/keydal/error.log',
log_date_format: 'YYYY-MM-DD HH:mm:ss',
merge_logs: true,
// Restart behaviour
autorestart: true,
watch: false,
max_restarts: 10,
min_uptime: '10s',
// Graceful shutdown
kill_timeout: 5000,
listen_timeout: 3000,
wait_ready: true
}]
};
# Start in production
pm2 start ecosystem.config.js --env production
# Auto-start on boot
pm2 startup
pm2 save
Zero-Downtime Deploys
pm2 reload rolls workers one by one. A new worker comes up, the old one is killed — users notice nothing. That is different from restart, which kills every worker simultaneously.
// server.js — graceful shutdown
const server = app.listen(PORT, () => {
console.log(`Server started on ${PORT}`);
if (process.send) process.send('ready'); // tell PM2 we are ready
});
process.on('SIGINT', gracefulShutdown);
process.on('SIGTERM', gracefulShutdown);
function gracefulShutdown() {
console.log('Shutting down gracefully...');
server.close(() => {
console.log('HTTP server closed');
db.pool.end(() => process.exit(0));
});
// Force exit after 10 seconds
setTimeout(() => process.exit(1), 10000);
}
Log Management
# Add log rotation (otherwise log files grow unbounded)
pm2 install pm2-logrotate
pm2 set pm2-logrotate:max_size 50M
pm2 set pm2-logrotate:retain 14
pm2 set pm2-logrotate:compress true
pm2 set pm2-logrotate:rotateInterval '0 0 * * *'
Monitoring
pm2 monit— local dashboard- PM2 Plus — cloud dashboard with a free tier
- Prometheus export via
pm2-prom-module - OpenTelemetry for Datadog or New Relic
Finding Memory Leaks
# Take a heap snapshot
pm2 restart myapp --node-args='--inspect'
# Chrome DevTools, chrome://inspect, Memory tab
# Or use clinic
npm i -g clinic
clinic doctor -- node server.js
# Send traffic for 30 seconds, then Ctrl+C
# An HTML report opens in the browser
Cluster vs Docker/Kubernetes
PM2 cluster mode is usually unnecessary in Kubernetes — K8s runs each pod as a single process and scales horizontally through the pod autoscaler. PM2 cluster makes sense on a single VPS or dedicated server.
Deploy Workflow
# Typical deploy script
#!/bin/bash
set -e
BRANCH=${1:-main}
cd /var/www/myapp
git fetch origin
git checkout $BRANCH
git pull origin $BRANCH
npm ci --production
npm run build # if any
pm2 reload ecosystem.config.js --env production
pm2 save
echo "Deploy done."
Conclusion
PM2 with cluster mode is the gold standard for single-server Node.js production. Ten minutes of configuration gets you all cores, zero-downtime deploys, memory limits, log rotation and auto-restart. It is the natural last stop before moving to Kubernetes.
PM2, Docker and Kubernetes setups for Node.js applications Contact us