Configure Node.js application clustering with PM2 and load balancing

Intermediate 25 min Apr 06, 2026 64 views
Ubuntu 24.04 Debian 12 AlmaLinux 9 Rocky Linux 9

Set up PM2 clustering to scale Node.js applications across CPU cores with NGINX load balancing. Monitor performance and optimize resource utilization for high-traffic production workloads.

Prerequisites

  • Root or sudo access
  • Basic Node.js knowledge
  • Understanding of reverse proxies

What this solves

Single-threaded Node.js applications can't utilize multiple CPU cores effectively, creating performance bottlenecks under high load. PM2 clustering spawns multiple application instances across all available cores while NGINX distributes incoming requests efficiently. This configuration maximizes server resource utilization and provides automatic process management with zero-downtime deployments.

Step-by-step configuration

Update system packages

Start by updating your package manager to ensure you get the latest versions of all dependencies.

sudo apt update && sudo apt upgrade -y
sudo dnf update -y

Install Node.js and npm

Install the latest LTS version of Node.js using NodeSource repository for consistent versions across distributions.

curl -fsSL https://deb.nodesource.com/setup_lts.x | sudo -E bash -
sudo apt install -y nodejs

Verify the installation:

node --version
npm --version

Install PM2 globally

PM2 is a production process manager that handles clustering, monitoring, and zero-downtime deployments for Node.js applications.

sudo npm install -g pm2

Verify PM2 installation:

pm2 --version

Create a sample Node.js application

Create a simple Express application to demonstrate clustering. This app will show which worker process handles each request.

mkdir -p /var/www/myapp
cd /var/www/myapp
{
  "name": "clustered-app",
  "version": "1.0.0",
  "description": "Node.js clustering demo",
  "main": "app.js",
  "scripts": {
    "start": "node app.js"
  },
  "dependencies": {
    "express": "^4.18.2"
  }
}
const express = require('express');
const app = express();
const port = 3000;

// Middleware to log requests
app.use((req, res, next) => {
  console.log(Worker ${process.pid} handling ${req.method} ${req.path});
  next();
});

// Basic route
app.get('/', (req, res) => {
  res.json({
    message: 'Hello from Node.js cluster!',
    worker_pid: process.pid,
    timestamp: new Date().toISOString()
  });
});

// Health check endpoint
app.get('/health', (req, res) => {
  res.json({
    status: 'healthy',
    worker_pid: process.pid,
    uptime: process.uptime()
  });
});

// CPU intensive route for testing
app.get('/cpu', (req, res) => {
  const start = Date.now();
  // Simulate CPU work
  while (Date.now() - start < 1000) {
    Math.random();
  }
  res.json({
    message: 'CPU intensive task completed',
    worker_pid: process.pid,
    duration: Date.now() - start
  });
});

app.listen(port, '127.0.0.1', () => {
  console.log(App running on port ${port}, PID: ${process.pid});
});

Install application dependencies

Install the Express framework and other dependencies for the demo application.

cd /var/www/myapp
npm install

Create PM2 ecosystem configuration

The ecosystem file defines how PM2 should manage your application, including clustering settings and environment variables.

module.exports = {
  apps: [{
    name: 'myapp',
    script: 'app.js',
    instances: 'max', // Use all available CPU cores
    exec_mode: 'cluster', // Enable clustering mode
    env: {
      NODE_ENV: 'production',
      PORT: 3000
    },
    // PM2 clustering options
    instance_var: 'INSTANCE_ID',
    
    // Monitoring and logging
    log_file: '/var/log/pm2/myapp.log',
    error_file: '/var/log/pm2/myapp-error.log',
    out_file: '/var/log/pm2/myapp-out.log',
    
    // Auto-restart settings
    max_memory_restart: '500M',
    min_uptime: '10s',
    max_restarts: 10,
    
    // Advanced options
    watch: false, // Don't watch files in production
    ignore_watch: ['node_modules', 'logs'],
    
    // Process management
    kill_timeout: 5000,
    listen_timeout: 3000,
    
    // Resource limits
    node_args: '--max-old-space-size=512'
  }]
};

Create PM2 log directory

Create the log directory and set proper permissions for PM2 to write log files.

sudo mkdir -p /var/log/pm2
sudo chown -R $USER:$USER /var/log/pm2

Start application with PM2 clustering

Launch your application using the ecosystem configuration. PM2 will automatically spawn one process per CPU core.

cd /var/www/myapp
pm2 start ecosystem.config.js

Check the status of your clustered application:

pm2 list
pm2 show myapp

Install and configure NGINX

Install NGINX to act as a load balancer and reverse proxy for your PM2 cluster.

sudo apt install -y nginx
sudo dnf install -y nginx

Configure NGINX load balancing

Create an NGINX configuration that load balances requests across your PM2 cluster instances. This configuration includes health checks and connection optimization.

upstream nodejs_backend {
    # PM2 cluster instances running on port 3000
    server 127.0.0.1:3000 max_fails=3 fail_timeout=30s;
    
    # Load balancing method
    least_conn; # Use least connections algorithm
    
    # Keep alive connections to backend
    keepalive 32;
}

server {
    listen 80;
    server_name example.com www.example.com;
    
    # Security headers
    add_header X-Frame-Options DENY;
    add_header X-Content-Type-Options nosniff;
    add_header X-XSS-Protection "1; mode=block";
    
    # Logging
    access_log /var/log/nginx/myapp_access.log;
    error_log /var/log/nginx/myapp_error.log;
    
    # Main application proxy
    location / {
        proxy_pass http://nodejs_backend;
        
        # Proxy headers
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        
        # Proxy timeouts
        proxy_connect_timeout 5s;
        proxy_send_timeout 60s;
        proxy_read_timeout 60s;
        
        # Buffer settings
        proxy_buffering on;
        proxy_buffer_size 4k;
        proxy_buffers 8 4k;
        
        # Connection settings
        proxy_cache_bypass $http_upgrade;
    }
    
    # Health check endpoint
    location /health {
        proxy_pass http://nodejs_backend;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
        access_log off;
    }
    
    # Static files (if you have any)
    location /static/ {
        alias /var/www/myapp/public/;
        expires 1y;
        add_header Cache-Control "public, immutable";
    }
    
    # Gzip compression
    gzip on;
    gzip_types text/plain text/css application/json application/javascript text/xml application/xml;
}

Enable NGINX site configuration

Enable the new site configuration and test the NGINX configuration for syntax errors.

sudo ln -s /etc/nginx/sites-available/myapp /etc/nginx/sites-enabled/
sudo nginx -t

Remove the default NGINX site if present:

sudo rm -f /etc/nginx/sites-enabled/default

Start and enable services

Start NGINX and configure PM2 to automatically start on system boot.

sudo systemctl enable --now nginx
sudo systemctl status nginx

Configure PM2 startup script:

pm2 startup
pm2 save
Note: The pm2 startup command will show you a command to run with sudo. Execute that command to enable PM2 auto-start on system boot.

Configure firewall rules

Allow HTTP traffic through the firewall while blocking direct access to the Node.js port.

sudo ufw allow 'Nginx Full'
sudo ufw enable
sudo firewall-cmd --add-service=http --permanent
sudo firewall-cmd --add-service=https --permanent
sudo firewall-cmd --reload

Monitor and optimize performance

Monitor PM2 cluster performance

Use PM2's built-in monitoring tools to track cluster performance and resource usage.

# Real-time monitoring
pm2 monit

Detailed process information

pm2 show myapp

View logs from all cluster instances

pm2 logs myapp

Memory and CPU usage

pm2 ls

Test load balancing

Verify that requests are being distributed across different worker processes.

# Test multiple requests to see different PIDs
for i in {1..10}; do
  curl -s http://localhost/ | grep worker_pid
done

Configure PM2 monitoring dashboard

Set up PM2's web monitoring interface for visual performance tracking. This step links to our system monitoring tutorial for comprehensive server monitoring.

# Enable PM2 web monitoring (optional)
pm2 web

View monitoring on http://localhost:9615

Optimize PM2 cluster configuration

Fine-tune your cluster configuration based on your application's memory and CPU usage patterns.

# Update the ecosystem.config.js with optimized settings
module.exports = {
  apps: [{
    name: 'myapp',
    script: 'app.js',
    instances: 4, // Specific number instead of 'max' for predictable resource usage
    exec_mode: 'cluster',
    
    // Memory optimization
    max_memory_restart: '300M', // Restart if memory exceeds 300MB
    node_args: '--max-old-space-size=256', // Limit V8 heap size
    
    // Performance settings
    instance_var: 'INSTANCE_ID',
    increment_var: 'PORT',
    
    // Graceful shutdowns
    kill_timeout: 3000,
    listen_timeout: 3000,
    
    env: {
      NODE_ENV: 'production',
      PORT: 3000
    }
  }]
};

Restart the application with new configuration:

pm2 reload myapp
pm2 save

Verify your setup

# Check PM2 cluster status
pm2 list

Test application response

curl -s http://localhost/ | jq .

Check NGINX status

sudo systemctl status nginx

Test load distribution

for i in {1..5}; do curl -s http://localhost/health | grep worker_pid; done

Monitor cluster performance

pm2 monit

Common issues

SymptomCauseFix
PM2 processes keep restartingMemory limit exceededIncrease max_memory_restart or optimize app memory usage
502 Bad Gateway from NGINXNode.js processes not runningCheck pm2 list and restart with pm2 reload myapp
All requests go to same workerSession affinity or connection poolingEnsure stateless application design and proper upstream configuration
High CPU usage on startupToo many instances for available coresSet specific instance count instead of 'max' in ecosystem config
Log files growing too largeNo log rotation configuredConfigure PM2 log rotation with pm2 install pm2-logrotate
NGINX connection errorsProxy timeout too lowIncrease proxy_read_timeout and proxy_send_timeout values

Next steps

Automated install script

Run this to automate the entire setup

Need help?

Don't want to manage this yourself?

We handle infrastructure performance optimization for businesses that depend on uptime. From initial setup to ongoing operations.