Node.js Production Deployment on a VPS with PM2 in 2026
Running node app.js in a terminal is not production deployment. If the terminal closes, the process dies. If the app crashes, it stays dead. If the server reboots, it doesn't restart. PM2 solves all of this.
PM2 Installation and Basic Use
npm install -g pm2
# Start your application
pm2 start app.js --name "my-app"
# With environment mode
pm2 start app.js --name "my-app" --env production
# Start on system boot
pm2 startup
pm2 save # Save current process list to resurrect on reboot
Ecosystem Configuration File
For production, use an ecosystem file instead of command-line flags:
// ecosystem.config.js
module.exports = {
apps: [{
name: 'my-api',
script: 'dist/server.js', // or build output
instances: 'max', // Use all CPU cores
exec_mode: 'cluster', // Fork for single-threaded, cluster for HTTP
env_production: {
NODE_ENV: 'production',
PORT: 3000
},
// Logging
log_file: '/var/log/myapp/combined.log',
error_file: '/var/log/myapp/error.log',
// Auto-restart on memory leak
max_memory_restart: '500M',
// Restart if file watches change (useful for simple deployments)
watch: false // Keep false in production
}]
};
pm2 start ecosystem.config.js --env production
Nginx as Reverse Proxy
server {
listen 443 ssl;
server_name api.yourdomain.com;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade'; # For WebSocket support
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_cache_bypass $http_upgrade;
}
}
Deployment Update Script
#!/bin/bash
# deploy.sh
set -e
cd /var/www/my-app
git pull origin main
npm ci --production
npm run build
pm2 reload my-api --update-env # Graceful zero-downtime reload
echo "Deployment complete"
Monitoring
pm2 monit # Real-time dashboard (CPU, memory, log)
pm2 logs my-api --lines 50 # Recent logs
pm2 status # Process overview