Node.js Application Deployment with PM2: Complete Production Guide
Introduction
PM2 (Process Manager 2) is a production-grade process manager for Node.js applications that provides automatic restarts, load balancing, monitoring, and deployment capabilities. It's the industry standard for keeping Node.js applications alive forever, managing application logs, and ensuring zero-downtime deployments. This comprehensive guide covers everything from basic PM2 installation to advanced production deployments, monitoring, and troubleshooting.
What You'll Learn
- Complete PM2 installation and configuration
- Application deployment and process management
- Cluster mode and load balancing
- Zero-downtime deployments and updates
- Log management and monitoring
- Startup scripts and automatic recovery
- PM2 ecosystem file configuration
- Performance optimization
- Integration with monitoring tools
- Troubleshooting common issues
Why PM2?
- Process Management: Keeps applications running forever
- Load Balancing: Built-in cluster mode for multi-core CPUs
- Zero Downtime: Reload applications without interruption
- Monitoring: Real-time application monitoring
- Log Management: Centralized log collection and rotation
- Startup Scripts: Automatic start on server reboot
- Easy Deployment: Simple deployment workflow
- Resource Management: Memory and CPU usage optimization
Prerequisites
- Node.js and npm installed (see Node.js installation guide)
- Ubuntu 20.04+, Debian 10+, CentOS 8+, or Rocky Linux 8+
- Root or sudo access
- Basic Node.js application ready for deployment
- At least 512MB RAM (1GB+ recommended)
Installation
Install PM2 Globally
# Install PM2
npm install pm2 -g
# Verify installation
pm2 --version
# Check PM2 is in PATH
which pm2
# If permission errors, see troubleshooting section
Update PM2
# Update PM2 to latest version
npm install pm2@latest -g
# Update PM2 daemon
pm2 update
Configuration
Basic Application Management
Start Applications
# Start simple Node.js app
pm2 start app.js
# Start with custom name
pm2 start app.js --name "my-api"
# Start with watch mode (auto-restart on file changes)
pm2 start app.js --watch
# Start with specific Node.js arguments
pm2 start app.js --node-args="--max-old-space-size=4096"
# Start npm script
pm2 start npm --name "my-app" -- start
# Start with environment variables
pm2 start app.js --env production
Manage Applications
# List all applications
pm2 list
pm2 ls
# Show detailed information
pm2 show my-api
# Stop application
pm2 stop my-api
pm2 stop 0
# Restart application
pm2 restart my-api
# Reload application (zero-downtime)
pm2 reload my-api
# Delete application from PM2
pm2 delete my-api
# Stop all applications
pm2 stop all
# Restart all applications
pm2 restart all
# Delete all applications
pm2 delete all
Cluster Mode for Load Balancing
Cluster mode runs multiple instances of your application to utilize all CPU cores:
# Start in cluster mode with max CPUs
pm2 start app.js -i max
# Start with specific number of instances
pm2 start app.js -i 4
# Start and auto-scale based on CPU usage
pm2 start app.js -i max --max-memory-restart 300M
# Scale up or down
pm2 scale my-api +2 # Add 2 more instances
pm2 scale my-api 4 # Set to exactly 4 instances
PM2 Ecosystem File
Create an ecosystem.config.js file for complex configurations:
# Generate ecosystem file
pm2 ecosystem
# Or create manually
cat > ecosystem.config.js <<'EOF'
module.exports = {
apps: [
{
name: 'api-server',
script: './app.js',
instances: 'max',
exec_mode: 'cluster',
env: {
NODE_ENV: 'development',
PORT: 3000
},
env_production: {
NODE_ENV: 'production',
PORT: 8080
},
error_file: './logs/err.log',
out_file: './logs/out.log',
log_date_format: 'YYYY-MM-DD HH:mm:ss Z',
max_memory_restart: '1G',
node_args: '--max-old-space-size=4096',
watch: false,
ignore_watch: ['node_modules', 'logs'],
max_restarts: 10,
min_uptime: '10s'
},
{
name: 'worker',
script: './worker.js',
instances: 2,
exec_mode: 'cluster',
cron_restart: '0 0 * * *', // Restart daily at midnight
env: {
NODE_ENV: 'development',
WORKER_TYPE: 'background'
},
env_production: {
NODE_ENV: 'production',
WORKER_TYPE: 'background'
}
}
]
};
EOF
# Start using ecosystem file
pm2 start ecosystem.config.js
# Start with specific environment
pm2 start ecosystem.config.js --env production
# Update applications from ecosystem file
pm2 reload ecosystem.config.js
Advanced Configuration Options
Complete ecosystem.config.js with all options:
module.exports = {
apps: [{
// Basic configuration
name: 'my-app',
script: './app.js',
cwd: '/var/www/my-app',
// Instance configuration
instances: 'max',
exec_mode: 'cluster',
// Environment variables
env: {
NODE_ENV: 'development',
PORT: 3000,
DATABASE_URL: 'mongodb://localhost:27017/dev'
},
env_production: {
NODE_ENV: 'production',
PORT: 8080,
DATABASE_URL: 'mongodb://production-db:27017/prod'
},
// Logs
error_file: './logs/err.log',
out_file: './logs/out.log',
log_file: './logs/combined.log',
log_date_format: 'YYYY-MM-DD HH:mm:ss Z',
merge_logs: true,
// Resource limits
max_memory_restart: '1G',
max_restarts: 10,
min_uptime: '10s',
listen_timeout: 3000,
kill_timeout: 5000,
// Node.js configuration
node_args: '--max-old-space-size=2048',
// Watch configuration
watch: false,
ignore_watch: ['node_modules', 'logs', '*.log'],
watch_options: {
followSymlinks: false
},
// Restart configuration
autorestart: true,
cron_restart: '0 0 * * *',
restart_delay: 4000,
// Advanced features
source_map_support: true,
instance_var: 'INSTANCE_ID',
// Post-deployment commands
post_update: ['npm install', 'echo Deployment finished']
}]
};
Deployment
Simple Deployment Workflow
# Initial deployment
pm2 start app.js --name "production-api" -i max
pm2 save
pm2 startup
# Update deployment
git pull origin main
npm install --production
pm2 reload production-api
# Or with ecosystem file
pm2 start ecosystem.config.js --env production
pm2 save
Zero-Downtime Deployment
# Create deployment script
cat > deploy.sh <<'EOF'
#!/bin/bash
echo "Starting deployment..."
# Pull latest code
git pull origin main
# Install dependencies
npm install --production
# Run database migrations (if needed)
npm run migrate
# Reload application with zero downtime
pm2 reload ecosystem.config.js --env production
# Check status
pm2 status
echo "Deployment completed successfully!"
EOF
chmod +x deploy.sh
# Run deployment
./deploy.sh
Startup Script Configuration
Configure PM2 to start applications on server reboot:
# Generate startup script
pm2 startup
# This will output a command like:
# sudo env PATH=$PATH:/usr/bin pm2 startup systemd -u username --hp /home/username
# Copy and run the generated command
# Save current PM2 process list
pm2 save
# Test by rebooting
sudo reboot
# After reboot, check PM2 is running
pm2 list
Blue-Green Deployment
Create a blue-green deployment strategy:
cat > ecosystem-blue.config.js <<EOF
module.exports = {
apps: [{
name: 'app-blue',
script: './app.js',
instances: 'max',
exec_mode: 'cluster',
env: {
PORT: 3000,
NODE_ENV: 'production'
}
}]
};
EOF
cat > ecosystem-green.config.js <<EOF
module.exports = {
apps: [{
name: 'app-green',
script: './app.js',
instances: 'max',
exec_mode: 'cluster',
env: {
PORT: 3001,
NODE_ENV: 'production'
}
}]
};
EOF
# Deploy green while blue is running
pm2 start ecosystem-green.config.js
# Switch Nginx/Load balancer to green
# Once stable, stop blue
pm2 stop app-blue
Example Express Application
Create a sample Express app for testing:
# Create app directory
mkdir -p /var/www/my-api
cd /var/www/my-api
# Initialize npm project
npm init -y
# Install dependencies
npm install express
# Create app.js
cat > app.js <<'EOF'
const express = require('express');
const app = express();
const port = process.env.PORT || 3000;
// Health check endpoint
app.get('/health', (req, res) => {
res.json({
status: 'healthy',
uptime: process.uptime(),
timestamp: new Date().toISOString(),
pid: process.pid
});
});
// Main endpoint
app.get('/', (req, res) => {
res.json({
message: 'API is running',
version: '1.0.0',
instance: process.env.INSTANCE_ID || 0
});
});
// Error handling
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).json({ error: 'Something went wrong!' });
});
app.listen(port, () => {
console.log(\`Server running on port \${port}\`);
});
// Graceful shutdown
process.on('SIGINT', () => {
console.log('Shutting down gracefully...');
process.exit(0);
});
EOF
# Start with PM2
pm2 start app.js -i max --name "my-api"
pm2 save
Monitoring
Real-time Monitoring
# Real-time monitoring dashboard
pm2 monit
# Simple list with metrics
pm2 list
# Detailed information about specific app
pm2 show my-api
# View logs in real-time
pm2 logs
# View logs for specific app
pm2 logs my-api
# View only error logs
pm2 logs --err
# View last 100 lines
pm2 logs --lines 100
# JSON formatted logs
pm2 logs --json
Log Management
# Flush all logs
pm2 flush
# Reload all logs
pm2 reloadLogs
# Install PM2 log rotate module
pm2 install pm2-logrotate
# Configure log rotation
pm2 set pm2-logrotate:max_size 10M
pm2 set pm2-logrotate:retain 7
pm2 set pm2-logrotate:compress true
pm2 set pm2-logrotate:dateFormat YYYY-MM-DD_HH-mm-ss
pm2 set pm2-logrotate:workerInterval 30
pm2 set pm2-logrotate:rotateInterval '0 0 * * *'
Performance Monitoring
# CPU and memory usage
pm2 list
# Show detailed metrics
pm2 describe my-api
# Monitor specific metrics
pm2 monitor my-api
# Check memory leaks
pm2 start app.js --max-memory-restart 500M
# Profile application
pm2 profile my-api
# Wait for profiling
pm2 profile:stop
PM2 Plus (Keymetrics) Integration
For advanced monitoring in production:
# Install PM2 Plus
pm2 install pm2-server-monit
# Link to PM2 Plus account (optional)
pm2 link [secret_key] [public_key] [machine_name]
# Monitor custom metrics in code
const pmx = require('@pm2/io');
const metric = pmx.metric({
name: 'Realtime users',
value: () => {
return userCount;
}
});
// Custom actions
pmx.action('Clear cache', (reply) => {
clearCache();
reply({ success: true });
});
Troubleshooting
Application Not Starting
# Check PM2 logs
pm2 logs my-api --err
# Check application with verbose logs
pm2 start app.js --log-date-format="YYYY-MM-DD HH:mm:ss"
# Check if port is already in use
sudo lsof -i :3000
# Kill process on port
sudo kill -9 $(sudo lsof -t -i:3000)
# Start with watch mode for debugging
pm2 start app.js --watch --ignore-watch="node_modules"
Memory Leaks
# Set memory restart threshold
pm2 start app.js --max-memory-restart 500M
# Monitor memory usage
pm2 monit
# Profile memory usage
pm2 profile my-api
# Enable heap snapshot
pm2 start app.js --node-args="--expose-gc"
# Take heap snapshot
kill -USR2 $(pm2 pid my-api)
High CPU Usage
# Check CPU usage
pm2 list
# Reduce number of instances
pm2 scale my-api 2
# Profile CPU usage
pm2 profile:cpu my-api
# Wait...
pm2 profile:cpu:stop
# Check for infinite loops in code
pm2 logs my-api --lines 1000
Application Crashes
# View crash logs
pm2 logs my-api --err --lines 100
# Increase restart attempts
pm2 start app.js --max-restarts 10 --min-uptime 10000
# Add error handling
pm2 start app.js --error /var/log/app-error.log
# Check for uncaught exceptions
process.on('uncaughtException', (err) => {
console.error('Uncaught Exception:', err);
process.exit(1);
});
process.on('unhandledRejection', (reason, promise) => {
console.error('Unhandled Rejection:', reason);
});
PM2 Won't Start on Reboot
# Remove old startup script
pm2 unstartup
# Regenerate startup script
pm2 startup
# Copy and run the generated command
# Save PM2 process list
pm2 save
# Verify saved processes
cat ~/.pm2/dump.pm2
# Test reboot
sudo reboot
Port Already in Use
# Find process using port
sudo lsof -i :3000
# Kill process
kill -9 $(lsof -t -i:3000)
# Or change port in ecosystem config
env: {
PORT: 3001
}
# Restart app
pm2 reload ecosystem.config.js
Security Best Practices
Run as Non-Root User
# Create dedicated user
sudo useradd -r -s /bin/bash pm2user
# Set ownership
sudo chown -R pm2user:pm2user /var/www/my-app
# Switch to user
sudo -u pm2user pm2 start app.js
# Setup startup script for that user
sudo su - pm2user
pm2 startup
# Run generated command as root
pm2 save
Environment Variables
# Never hardcode secrets
# Use ecosystem file with env vars
env_production: {
NODE_ENV: 'production',
DATABASE_URL: process.env.DATABASE_URL,
API_KEY: process.env.API_KEY
}
# Or use .env file (install dotenv)
npm install dotenv
# In app.js
require('dotenv').config();
# Start with env file
pm2 start app.js --env production
Log Security
# Restrict log file permissions
chmod 640 /var/log/pm2/*.log
# Use log rotation
pm2 install pm2-logrotate
pm2 set pm2-logrotate:retain 7
# Don't log sensitive data
// In code
console.log('User authenticated'); // Good
console.log('Password:', password); // Bad!
Update Regularly
# Update PM2
npm update pm2 -g
# Update application dependencies
npm update
npm audit fix
# Reload application
pm2 reload all
Performance Optimization
Cluster Mode Optimization
# Optimal instances = CPU cores
pm2 start app.js -i max
# Or specific number
pm2 start app.js -i 4
# Monitor and adjust
pm2 monit
# If low CPU usage across instances, reduce number
pm2 scale my-api 2
Memory Optimization
# Set memory limits
pm2 start app.js --max-memory-restart 500M
# Increase Node.js heap size
pm2 start app.js --node-args="--max-old-space-size=4096"
# Enable garbage collection logs
pm2 start app.js --node-args="--trace-gc"
Load Balancing with Nginx
upstream nodejs_backend {
least_conn;
server 127.0.0.1:3000;
server 127.0.0.1:3001;
server 127.0.0.1:3002;
server 127.0.0.1:3003;
}
server {
listen 80;
server_name api.example.com;
location / {
proxy_pass http://nodejs_backend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
Caching Strategy
// Implement caching in app
const NodeCache = require('node-cache');
const cache = new NodeCache({ stdTTL: 600 });
app.get('/api/data', async (req, res) => {
const cached = cache.get('data');
if (cached) {
return res.json(cached);
}
const data = await fetchData();
cache.set('data', data);
res.json(data);
});
Conclusion
You've successfully configured PM2 for production Node.js application deployment with comprehensive process management, monitoring, and optimization strategies. PM2 ensures your applications stay online, perform efficiently, and can be managed with ease.
Key Takeaways
- PM2 provides production-grade process management
- Cluster mode enables load balancing across CPU cores
- Zero-downtime reloads keep applications available during updates
- Ecosystem files enable consistent, reproducible deployments
- Automatic startup ensures applications survive reboots
- Built-in monitoring and logging simplify operations
Best Practices
- Always use ecosystem.config.js for production
- Enable startup scripts for automatic recovery
- Implement log rotation to manage disk space
- Use cluster mode to utilize all CPU cores
- Set memory limits to prevent server crashes
- Monitor applications regularly
- Use zero-downtime reloads for updates
- Keep PM2 and Node.js updated
Next Steps
- Set up monitoring dashboards (PM2 Plus, Grafana)
- Implement automated deployments (CI/CD)
- Configure reverse proxy (Nginx/Apache)
- Set up SSL/TLS certificates
- Implement comprehensive error tracking
- Create disaster recovery procedures
- Set up staging and production environments
PM2 transforms Node.js application deployment from complex to simple, providing the tools needed for reliable, scalable production deployments!
Happy deploying!


