Difficulty: Intermediate
In production, you never run a Node.js application with just `node app.js`. If the process crashes, it stays down. If the server has 8 CPU cores, a single Node.js process only uses one. Process managers solve both problems: they automatically restart crashed processes and can run multiple instances to utilize all CPU cores. PM2 is the most popular process manager for Node.js production deployments.
PM2 (Process Manager 2) provides process management, monitoring, and load balancing out of the box. Starting an app with `pm2 start app.js` runs it as a background daemon that automatically restarts on crashes. PM2 tracks CPU usage, memory consumption, restart counts, and uptime for each process. The `pm2 monit` command provides a real-time dashboard, and `pm2 logs` streams application output.
Cluster mode is one of PM2's most powerful features. Node.js is single-threaded, meaning a single process can only use one CPU core. By running `pm2 start app.js -i max`, PM2 forks your application into multiple processes (one per CPU core) and load-balances incoming connections across them. This multiplies your application's throughput without any code changes. Each worker process handles requests independently, and if one crashes, the others continue serving while PM2 restarts the failed one.
Graceful shutdown is essential for zero-downtime deployments and clean process management. When PM2 sends a shutdown signal (SIGINT), your application should stop accepting new connections, finish processing current requests, close database connections, and then exit. Without graceful shutdown, active requests are abruptly terminated, database transactions may be left in an inconsistent state, and users experience errors during deployments.
The PM2 ecosystem file (ecosystem.config.js) defines your application's configuration in code - script path, environment variables, cluster instances, log paths, and deployment settings. This file is version-controlled and ensures consistent configuration across team members and environments. PM2 also supports zero-downtime reloads with `pm2 reload`, which starts new worker processes before stopping old ones, ensuring no requests are dropped during deployment.
// PM2 CLI commands (run in terminal, not in code)
// pm2 start app.js # Start in fork mode
// pm2 start app.js -i max # Cluster mode (all CPU cores)
// pm2 start app.js -i 4 # 4 cluster instances
// pm2 start app.js --name myapp # Named process
// pm2 list # List all processes
// pm2 monit # Real-time monitoring
// pm2 logs # Stream all logs
// pm2 logs myapp --lines 100 # Last 100 lines for myapp
// pm2 restart myapp # Restart (downtime)
// pm2 reload myapp # Zero-downtime reload
// pm2 stop myapp # Stop process
// pm2 delete myapp # Remove from PM2
// pm2 save # Save process list
// pm2 startup # Auto-start on boot
// ecosystem.config.js
const ecosystemConfig = {
apps: [{
name: 'myapp-api',
script: './src/index.js',
instances: 'max',
exec_mode: 'cluster',
autorestart: true,
max_memory_restart: '500M',
watch: false,
env: {
NODE_ENV: 'development',
PORT: 3000
},
env_production: {
NODE_ENV: 'production',
PORT: 8080
}
}]
};
console.log('Ecosystem config:', ecosystemConfig.apps[0].name);
console.log('Instances:', ecosystemConfig.apps[0].instances);
console.log('Mode:', ecosystemConfig.apps[0].exec_mode);
console.log('Max memory:', ecosystemConfig.apps[0].max_memory_restart);
The ecosystem config defines everything PM2 needs to run your app. 'instances: max' uses all CPU cores. 'exec_mode: cluster' enables load balancing. 'max_memory_restart' automatically restarts workers that exceed the memory limit. Different env blocks provide environment-specific variables.
const express = require('express');
const app = express();
// Simulate database connection
const db = { connected: true, close: () => { db.connected = false; } };
app.get('/health', (req, res) => {
res.json({ status: 'ok', db: db.connected });
});
const server = app.listen(3000, () => {
console.log('Server running on port 3000');
});
// Graceful shutdown handler
function gracefulShutdown(signal) {
console.log(`\n${signal} received. Starting graceful shutdown...`);
// Stop accepting new connections
server.close(() => {
console.log('HTTP server closed');
// Close database connections
db.close();
console.log('Database connections closed');
// Exit cleanly
console.log('Graceful shutdown complete');
process.exit(0);
});
// Force shutdown after 10 seconds
setTimeout(() => {
console.error('Forced shutdown after timeout');
process.exit(1);
}, 10000);
}
// Listen for shutdown signals
process.on('SIGTERM', () => gracefulShutdown('SIGTERM'));
process.on('SIGINT', () => gracefulShutdown('SIGINT'));
// PM2 graceful shutdown
process.on('message', (msg) => {
if (msg === 'shutdown') {
gracefulShutdown('PM2 shutdown');
}
});
console.log('Graceful shutdown handlers registered');
console.log('Handles: SIGTERM, SIGINT, PM2 message');
The shutdown handler stops accepting new connections with server.close(), waits for active requests to finish, closes database connections, and exits. A timeout forces shutdown if cleanup takes too long. PM2 sends a 'shutdown' message via IPC, which is handled separately from OS signals.
const cluster = require('cluster');
const os = require('os');
if (cluster.isPrimary) {
const numCPUs = os.cpus().length;
console.log(`Primary process ${process.pid}`);
console.log(`Starting ${numCPUs} workers...`);
// Fork workers
for (let i = 0; i < numCPUs; i++) {
cluster.fork();
}
// Restart crashed workers
cluster.on('exit', (worker, code, signal) => {
console.log(`Worker ${worker.process.pid} died (code: ${code}). Restarting...`);
cluster.fork();
});
cluster.on('online', (worker) => {
console.log(`Worker ${worker.process.pid} is online`);
});
} else {
// Workers share the TCP connection
const express = require('express');
const app = express();
app.get('/', (req, res) => {
res.json({ worker: process.pid });
});
app.listen(3000, () => {
console.log(`Worker ${process.pid} listening on port 3000`);
});
}
// Note: PM2 cluster mode handles all this automatically!
// Just use: pm2 start app.js -i max
console.log('Process PID:', process.pid);
console.log('Is primary:', cluster.isPrimary);
console.log('CPU cores:', os.cpus().length);
The Node.js cluster module is what PM2 uses internally. The primary process forks worker processes that share the same port. If a worker crashes, the primary restarts it. In practice, use PM2 instead of coding this manually - it handles clustering, monitoring, and log management.
PM2, cluster mode, graceful shutdown, zero-downtime restart, process monitoring, ecosystem file