PM2 Process Manager
Keep your Node.js app running forever - clustering, monitoring, and zero-downtime deployments.
5 min read
Why PM2?#
Node.js runs as a single process. If it crashes, your app dies. PM2 solves this:
- Auto-restart on crash
- Cluster mode - use all CPU cores
- Zero-downtime deployments
- Monitoring and logs
- Startup scripts - survive reboots
Getting Started#
bash
npm install -g pm2
Basic Commands#
bash
# Start application
pm2 start src/index.js
# With name
pm2 start src/index.js --name my-api
# View running processes
pm2 list
# View logs
pm2 logs
pm2 logs my-api
# Monitor
pm2 monit
# Stop
pm2 stop my-api
# Restart
pm2 restart my-api
# Delete from PM2
pm2 delete my-api
Ecosystem File#
Configure everything in one file:
javascript
// ecosystem.config.cjs
module.exports = {
apps: [{
name: 'my-api',
script: 'src/index.js',
instances: 'max', // Use all CPUs
exec_mode: 'cluster', // Cluster mode
autorestart: true,
watch: false,
max_memory_restart: '1G',
env: {
NODE_ENV: 'development',
PORT: 3000,
},
env_production: {
NODE_ENV: 'production',
PORT: 3000,
},
}],
};
Start with ecosystem file#
bash
# Development
pm2 start ecosystem.config.cjs
# Production
pm2 start ecosystem.config.cjs --env production
Cluster Mode#
Use all CPU cores:
javascript
// ecosystem.config.cjs
module.exports = {
apps: [{
name: 'my-api',
script: 'src/index.js',
instances: 'max', // Or specific number: 4
exec_mode: 'cluster',
}],
};
bash
# Or via CLI
pm2 start src/index.js -i max
pm2 start src/index.js -i 4 # 4 instances
Cluster Considerations
Cluster mode runs multiple Node processes. Session state isn't shared - use Redis for sessions. Each process has its own memory.
Zero-Downtime Reload#
Update code without dropping connections:
bash
# Graceful reload (zero-downtime)
pm2 reload my-api
# Hard restart (has downtime)
pm2 restart my-api
Graceful Shutdown#
Handle reload signals in your app:
javascript
// src/index.js
process.on('SIGINT', gracefulShutdown);
process.on('SIGTERM', gracefulShutdown);
async function gracefulShutdown() {
console.log('Graceful shutdown started');
// Stop accepting new connections
server.close(async () => {
console.log('HTTP server closed');
// Close database connections
await mongoose.connection.close();
console.log('MongoDB connection closed');
process.exit(0);
});
// Force exit after timeout
setTimeout(() => {
console.log('Forced shutdown');
process.exit(1);
}, 10000);
}
Startup Script#
Survive server reboots:
bash
# Generate startup script
pm2 startup
# Save current process list
pm2 save
# To remove
pm2 unstartup
Log Management#
bash
# View logs
pm2 logs
# View specific app logs
pm2 logs my-api
# Clear logs
pm2 flush
# Log rotation
pm2 install pm2-logrotate
pm2 set pm2-logrotate:max_size 10M
pm2 set pm2-logrotate:retain 7
Custom log paths#
javascript
// ecosystem.config.cjs
module.exports = {
apps: [{
name: 'my-api',
script: 'src/index.js',
output: './logs/out.log',
error: './logs/error.log',
log_date_format: 'YYYY-MM-DD HH:mm:ss',
merge_logs: true, // Merge cluster logs
}],
};
Monitoring#
Built-in Monitor#
bash
pm2 monit
PM2 Plus (Cloud Dashboard)#
bash
pm2 link <secret> <public>
Prometheus Metrics#
bash
pm2 install pm2-prometheus-exporter
Deployment#
Via SSH#
javascript
// ecosystem.config.cjs
module.exports = {
apps: [{ /* ... */ }],
deploy: {
production: {
user: 'deploy',
host: 'server.example.com',
ref: 'origin/main',
repo: 'git@github.com:user/repo.git',
path: '/var/www/my-api',
'pre-deploy': 'git fetch --all',
'post-deploy': 'npm ci && pm2 reload ecosystem.config.cjs --env production',
'pre-setup': 'mkdir -p /var/www/my-api',
},
},
};
bash
# First time setup
pm2 deploy production setup
# Deploy
pm2 deploy production
# Rollback
pm2 deploy production revert 1
Environment Variables#
javascript
// ecosystem.config.cjs
module.exports = {
apps: [{
name: 'my-api',
script: 'src/index.js',
env: {
NODE_ENV: 'development',
PORT: 3000,
},
env_staging: {
NODE_ENV: 'staging',
PORT: 3000,
},
env_production: {
NODE_ENV: 'production',
PORT: 3000,
},
}],
};
bash
pm2 start ecosystem.config.cjs --env production
Loading from .env#
javascript
// ecosystem.config.cjs
const dotenv = require('dotenv');
dotenv.config();
module.exports = {
apps: [{
name: 'my-api',
script: 'src/index.js',
env: {
...process.env,
},
}],
};
Memory Management#
javascript
// ecosystem.config.cjs
module.exports = {
apps: [{
name: 'my-api',
script: 'src/index.js',
max_memory_restart: '500M', // Restart if memory exceeds
node_args: '--max-old-space-size=450', // Node heap limit
}],
};
Complete Production Setup#
javascript
// ecosystem.config.cjs
module.exports = {
apps: [{
name: 'my-api',
script: 'src/index.js',
// Cluster mode
instances: 'max',
exec_mode: 'cluster',
// Restart behavior
autorestart: true,
max_restarts: 10,
min_uptime: '10s',
max_memory_restart: '1G',
// Logs
output: './logs/out.log',
error: './logs/error.log',
log_date_format: 'YYYY-MM-DD HH:mm:ss',
merge_logs: true,
// Environment
env_production: {
NODE_ENV: 'production',
PORT: 3000,
},
// Graceful shutdown
kill_timeout: 5000,
wait_ready: true,
listen_timeout: 10000,
}],
};
Signal Ready#
javascript
// src/index.js
const server = app.listen(config.port, () => {
console.log(`Server running on port ${config.port}`);
// Tell PM2 we're ready
if (process.send) {
process.send('ready');
}
});
Comparison with Alternatives#
| Feature | PM2 | Forever | Nodemon | systemd |
|---|---|---|---|---|
| Auto-restart | Yes | Yes | Yes | Yes |
| Cluster mode | Yes | No | No | Manual |
| Zero-downtime | Yes | No | No | Manual |
| Monitoring | Yes | Basic | No | journalctl |
| Log management | Yes | Basic | No | journald |
| Deployment | Yes | No | No | No |
| Best for | Production | Simple | Dev | Servers |
Key Takeaways#
- Use cluster mode - Utilize all CPU cores
- Set up startup script - Survive reboots
- Configure graceful shutdown - Clean restarts
- Use ecosystem file - Version control your config
- Set memory limits - Prevent memory leaks from killing server
Production Recipe
bash
pm2 start ecosystem.config.cjs --env production
pm2 save
pm2 startup
Three commands: Start clustered, save state, survive reboots. Production ready.
Continue Learning
Ready to level up your skills?
Explore more guides and tutorials to deepen your understanding and become a better developer.