PM2 for Node.js in production: process management, logging, and clustering
10 min read · 15-Jul-2024
villagehosting.in team
15 July 2024
node app.js is not production-grade. It crashes on error and stays down until you notice. PM2 restarts crashed processes, clusters across CPU cores, manages logs, and survives server reboots. Here is how to set it up correctly.
pm2 save + pm2 startup is mandatory
After configuring your PM2 processes, run pm2 save and then the output of pm2 startup (a systemd command). Without these two steps, your Node.js apps won't restart after a VPS reboot. This is the single most common 'why is my app down' question after a server restart.
What PM2 does
PM2 (Process Manager 2) is a Node.js process manager. It:
- Keeps your app running: automatically restarts on crash
- Cluster mode: spawns one process per CPU core, multiplying throughput
- Zero-downtime reload: restarts processes one at a time, keeping others serving requests
- Log management: aggregates stdout/stderr logs with rotation
- Startup script: configures systemd to restart PM2 (and all apps) after server reboot
- Monitoring: real-time CPU and memory monitoring per process
Installation
npm install -g pm2
Verify:
pm2 --version
Starting your application
# Start with a name
pm2 start app.js --name "myapp"
# Or start with npm script
pm2 start npm --name "myapp" -- start
# Or start with explicit node path
pm2 start /usr/bin/node --name "myapp" -- app.js
Check status:
pm2 status
pm2 ls # same command, shorter alias
Output:
┌─────┬────────┬─────────────┬─────────┬─────────┬──────────┬────────┬──────┬──────────┐
│ id │ name │ namespace │ version │ mode │ pid │ uptime │ cpu │ mem │
├─────┼────────┼─────────────┼─────────┼─────────┼──────────┼────────┼──────┼──────────┤
│ 0 │ myapp │ default │ 1.0.0 │ fork │ 12345 │ 2m │ 0.1% │ 45.3mb │
└─────┴────────┴─────────────┴─────────┴─────────┴──────────┴────────┴──────┴──────────┘
Cluster mode
Cluster mode spawns multiple processes. Each process handles separate requests. Node.js is single-threaded — cluster mode is how you use all CPU cores:
# Use all available cores
pm2 start app.js --name "myapp" -i max
# Use a specific number of cores
pm2 start app.js --name "myapp" -i 4
# Use 2 fewer than max (leaves cores for OS and other services)
pm2 start app.js --name "myapp" -i -2
On a 4-core VPS, -i max spawns 4 processes. Each handles its own requests. Total throughput: 4× a single process.
Requirements for cluster mode: your application must be stateless — no in-memory state shared between processes. Use Redis for sessions, caches, and pub/sub.
Zero-downtime reload
A regular restart (pm2 restart myapp) kills all processes and starts new ones — brief downtime. Reload keeps at least one process running at all times:
pm2 reload myapp
PM2 restarts each process one at a time, waiting for the new process to be ready before killing the old one. For cluster mode with 4 processes, you always have at least 3 serving traffic during a reload.
Use reload for code updates. Use restart only when you need to force-clear memory or process state.
Ecosystem file (recommended for production)
Instead of command-line flags, define your app configuration in an ecosystem file:
// ecosystem.config.js
module.exports = {
apps: [
{
name: 'myapp',
script: 'app.js',
instances: 'max',
exec_mode: 'cluster',
watch: false, // Don't watch files in production
max_memory_restart: '512M',
env: {
NODE_ENV: 'development',
PORT: 3000,
},
env_production: {
NODE_ENV: 'production',
PORT: 3000,
DATABASE_URL: 'postgresql://user:pass@localhost/db',
},
log_file: '/home/myapp/logs/combined.log',
error_file: '/home/myapp/logs/error.log',
out_file: '/home/myapp/logs/out.log',
log_date_format: 'YYYY-MM-DD HH:mm:ss Z',
merge_logs: true,
},
],
};
Start with the ecosystem file:
# Development
pm2 start ecosystem.config.js
# Production (uses env_production)
pm2 start ecosystem.config.js --env production
Never put secrets directly in the ecosystem file if you commit it to git. Use environment variables or a .env file loaded separately.
Environment variables
Load from .env:
# Install dotenv
npm install dotenv
# In your app.js, add at the very top:
require('dotenv').config();
Or pass environment variables via PM2:
pm2 start app.js --env-from-file .env
Or set them in the ecosystem file's env_production block.
Log management
View logs:
pm2 logs # All apps, live tail
pm2 logs myapp # Specific app
pm2 logs myapp --lines 100 # Last 100 lines
pm2 logs --err # Only errors
Log rotation (prevents logs filling disk):
pm2 install pm2-logrotate
# Configure
pm2 set pm2-logrotate:max_size 50M # Rotate at 50 MB
pm2 set pm2-logrotate:retain 7 # Keep 7 days of logs
pm2 set pm2-logrotate:compress true # Gzip old logs
Startup script: survive reboots
After server restart, PM2 itself doesn't start automatically. Set up a startup script:
# Generate startup command for your OS
pm2 startup
# Follow the instruction it prints — usually:
sudo env PATH=$PATH:/usr/bin /usr/lib/node_modules/pm2/bin/pm2 startup systemd -u youruser --hp /home/youruser
# Save current process list (so they restart automatically)
pm2 save
Test the setup:
sudo reboot
# After reboot, SSH back in and run:
pm2 status
# Your apps should be running
Monitoring
# Real-time dashboard
pm2 monit
# Get process info
pm2 show myapp
# List all processes
pm2 list
pm2 monit shows:
- CPU % per process
- Memory per process
- Request count
- Error log tail
Common operations
# Stop (kills processes but keeps PM2 config)
pm2 stop myapp
# Delete (removes from PM2 config entirely)
pm2 delete myapp
# Restart (full kill + restart)
pm2 restart myapp
# Zero-downtime reload
pm2 reload myapp
# Reload all apps
pm2 reload all
# Update PM2 itself
pm2 update
npm install -g pm2@latest
Deployment workflow
# On server, pull latest code
git pull origin main
# Install new dependencies
npm ci --production
# Run database migrations
npm run migrate
# Zero-downtime reload
pm2 reload myapp
For CI/CD (GitHub Actions):
# .github/workflows/deploy.yml
- name: Deploy to VPS
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.VPS_HOST }}
username: ${{ secrets.VPS_USER }}
key: ${{ secrets.VPS_SSH_KEY }}
script: |
cd /home/myapp/yourproject
git pull origin main
npm ci --production
npm run migrate
pm2 reload myapp
PM2 is the right tool for Node.js in production on a VPS. For multiple applications or teams, consider graduating to Docker Compose (better isolation) or a managed Kubernetes cluster (full autoscaling) — but PM2 is where most successful Indian Node.js applications run in production today.