Skip to main content
In production, you need a process manager to keep your Express application running reliably, handle crashes, and manage multiple instances for load balancing.

Why Use a Process Manager?

Process managers provide essential production features:
  • Auto-restart - Automatically restart your app if it crashes
  • Performance monitoring - Track CPU, memory, and request metrics
  • Clustering - Run multiple instances to utilize all CPU cores
  • Log management - Centralized logging and rotation
  • Zero-downtime deploys - Reload your app without dropping connections
  • Process lifecycle management - Start, stop, and restart with ease
Never run Node.js directly in production - Always use a process manager or container orchestration system.
PM2 is the most popular process manager for Node.js applications.

Installation

npm install -g pm2

Basic Usage

1

Start Your Application

pm2 start app.js
2

View Running Processes

pm2 list
3

Monitor Your App

pm2 monit
4

View Logs

pm2 logs

Cluster Mode

Run multiple instances to utilize all CPU cores.
# Start app in cluster mode with max instances
pm2 start app.js -i max

# Or specify number of instances
pm2 start app.js -i 4
Use -i max to automatically create one instance per CPU core.

Ecosystem File

Create a ecosystem.config.js file for advanced configuration.
module.exports = {
  apps: [{
    name: 'express-app',
    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',
    merge_logs: true,
    watch: false,
    max_memory_restart: '1G',
    min_uptime: '10s',
    max_restarts: 10
  }]
};

PM2 Commands

# Start app
pm2 start app.js --name "my-app"

# Stop app
pm2 stop my-app

# Restart app
pm2 restart my-app

# Reload (zero-downtime)
pm2 reload my-app

# Delete from PM2
pm2 delete my-app

# Stop all
pm2 stop all

# Restart all
pm2 restart all
# List all processes
pm2 list

# Monitor CPU/Memory
pm2 monit

# Show process details
pm2 show my-app

# View logs
pm2 logs

# View logs for specific app
pm2 logs my-app

# Clear logs
pm2 flush
# Generate startup script
pm2 startup

# Save current process list
pm2 save

# Restore saved processes
pm2 resurrect

# Disable startup
pm2 unstartup

Zero-Downtime Deployment

Update your app without dropping connections.
# 1. Pull latest code
git pull origin main

# 2. Install dependencies
npm install

# 3. Reload app with zero downtime
pm2 reload ecosystem.config.js --update-env
Use reload instead of restart to avoid dropping active connections.

systemd

systemd is the standard init system for Linux distributions.

Creating a Service

Create /etc/systemd/system/express-app.service:
[Unit]
Description=Express.js Application
After=network.target

[Service]
Type=simple
User=nodejs
WorkingDirectory=/home/nodejs/app
Environment="NODE_ENV=production"
Environment="PORT=3000"
ExecStart=/usr/bin/node /home/nodejs/app/server.js
Restart=on-failure
RestartSec=10
StandardOutput=syslog
StandardError=syslog
SyslogIdentifier=express-app

[Install]
WantedBy=multi-user.target

systemd Commands

1

Enable the Service

sudo systemctl enable express-app
2

Start the Service

sudo systemctl start express-app
3

Check Status

sudo systemctl status express-app
4

View Logs

sudo journalctl -u express-app -f
# Start
sudo systemctl start express-app

# Stop
sudo systemctl stop express-app

# Restart
sudo systemctl restart express-app

# Reload configuration
sudo systemctl daemon-reload

Docker

Run your Express app in containers for consistency across environments.

Dockerfile

FROM node:18-alpine

# Create app directory
WORKDIR /usr/src/app

# Install dependencies
COPY package*.json ./
RUN npm ci --only=production

# Copy app source
COPY . .

# Expose port
EXPOSE 3000

# Run as non-root user
USER node

# Start app
CMD ["node", "server.js"]

Docker Compose

version: '3.8'

services:
  app:
    build: .
    ports:
      - "3000:3000"
    environment:
      - NODE_ENV=production
      - PORT=3000
    restart: unless-stopped
    volumes:
      - ./logs:/usr/src/app/logs
    healthcheck:
      test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:3000/health"]
      interval: 30s
      timeout: 10s
      retries: 3
# Build image
docker build -t express-app .

# Run container
docker run -d -p 3000:3000 --name express-app express-app

# View logs
docker logs -f express-app

# Stop container
docker stop express-app

# Remove container
docker rm express-app

# With Docker Compose
docker-compose up -d
docker-compose logs -f
docker-compose down

Kubernetes

For large-scale deployments, use Kubernetes.
apiVersion: apps/v1
kind: Deployment
metadata:
  name: express-app
spec:
  replicas: 3
  selector:
    matchLabels:
      app: express-app
  template:
    metadata:
      labels:
        app: express-app
    spec:
      containers:
      - name: express-app
        image: express-app:latest
        ports:
        - containerPort: 3000
        env:
        - name: NODE_ENV
          value: "production"
        resources:
          limits:
            memory: "512Mi"
            cpu: "500m"
          requests:
            memory: "256Mi"
            cpu: "250m"
        livenessProbe:
          httpGet:
            path: /health
            port: 3000
          initialDelaySeconds: 30
          periodSeconds: 10
        readinessProbe:
          httpGet:
            path: /ready
            port: 3000
          initialDelaySeconds: 5
          periodSeconds: 5

Node.js Built-in Cluster

For simple clustering without external dependencies.
const cluster = require('cluster');
const os = require('os');
const express = require('express');

if (cluster.isMaster) {
  const numCPUs = os.cpus().length;
  
  console.log(`Master process ${process.pid} starting`);
  console.log(`Forking ${numCPUs} workers`);
  
  // Fork workers
  for (let i = 0; i < numCPUs; i++) {
    cluster.fork();
  }
  
  // Handle worker exit
  cluster.on('exit', (worker, code, signal) => {
    console.log(`Worker ${worker.process.pid} died`);
    console.log('Starting a new worker');
    cluster.fork();
  });
  
} else {
  // Workers share the TCP connection
  const app = express();
  
  app.get('/', (req, res) => {
    res.send(`Hello from worker ${process.pid}`);
  });
  
  app.listen(3000, () => {
    console.log(`Worker ${process.pid} started`);
  });
}
PM2 provides more features than the built-in cluster module. Use PM2 unless you have specific requirements.

Comparison

FeaturePM2systemdDockerKubernetes
Auto-restart
ClusteringManualManual
MonitoringBasicExternal
Log management
Zero-downtimeManual
Learning curveEasyMediumMediumHard
Best forVPS/VMLinux serversAny OSLarge scale

Best Practices

Use PM2 for most cases - It’s the easiest and most feature-rich option for VPS/VM deployments.
Enable clustering - Utilize all CPU cores for better performance.
Set memory limits - Prevent memory leaks from crashing your server.
  • Always use a process manager in production
  • Enable automatic restarts on failure
  • Configure proper logging
  • Set up startup scripts for auto-start on boot
  • Monitor resource usage regularly
  • Implement health checks
  • Use environment variables for configuration

Next Steps

Build docs developers (and LLMs) love