Back to blog

How to Deploy and Manage Node.js Apps in Production Using PM2

Spawning a Node.js process using raw commands like node server.js is fine for local development. However, running your production server this way is highly dangerous. If an unhandled exception triggers a crash, or if the server hosts run out of memory, the Node.js process will exit, taking your website offline.

To keep Node.js applications alive, handle crashes, utilize multi-core server processors, and configure log rotations, you need a Process Manager. The industry standard for Node.js is PM2.

In this guide, we will explore PM2, configure a production-grade ecosystem.config.js setup, enable multi-core Cluster Mode, and configure zero-downtime deployments.

Why You Need PM2

PM2 acts as a daemon process manager. Once installed, it runs in the background and supervises your Node.js processes:

  • Automatic Restarts: If your application crashes due to an error, PM2 intercepts the crash and spins up a new instance in milliseconds.
  • CPU Core Utilization (Cluster Mode): By default, Node.js runs on a single CPU core. PM2 can duplicate your application across all available CPU cores without code changes.
  • Graceful Reloads: Deploy updates without dropping active user connections (zero-downtime).
  • System Startup Persistence: Automatically restarts your Node.js applications if the host virtual machine reboots.

Configuring PM2 with ecosystem.config.js

While you can run PM2 using CLI flags, the best practice is declaring configurations in a structured file named ecosystem.config.js in your repository root.

module.exports = {
  apps: [
    {
      name: 'my-node-app',
      script: './dist/server.js',
      
      // Cluster mode setup
      instances: 'max', // Spawns one instance per CPU core
      exec_mode: 'cluster', // Enables clustering
      
      // Auto-restart configuration
      watch: false, // Turn off in production
      max_memory_restart: '1G', // Restart if RAM usage exceeds 1GB
      autorestart: true,
      
      // Log management paths
      error_file: './logs/err.log',
      out_file: './logs/out.log',
      log_date_format: 'YYYY-MM-DD HH:mm:ss Z',
      
      // Environment variables
      env_production: {
        NODE_ENV: 'production',
        PORT: 3000,
      },
    },
  ],
};

Running PM2: Essential Commands

Once your config file is written, deploy and manage your processes using these CLI commands:

# Start your application in production mode
pm2 start ecosystem.config.js --env production

# Check status of running instances
pm2 list

# Monitor real-time CPU, memory, and logs
pm2 monit

# Save the process list to revive on system reboot
pm2 save

Zero-Downtime: Restart vs. Reload

When you update your code and pull changes to production:

  • Running pm2 restart kills all running instances instantly and starts them back up. During this transition, users visiting your site will see connection refused errors.
  • Running pm2 reload performs a graceful reload. It restarts instances sequentially. PM2 keeps older instances alive to serve active HTTP requests until the new instances have finished booting up and are ready to take over, achieving zero-downtime.
# Apply code updates gracefully with zero downtime
pm2 reload ecosystem.config.js

Log Maintenance: PM2 Logrotate

If your application logs heavily, PM2’s out files can grow to gigabytes, filling up your server's SSD.

To prevent this, install the PM2-logrotate plugin. It automatically splits, compresses, and purges older logs:

pm2 install pm2-logrotate

Configure log limits:

# Force log rotation when log size hits 10MB
pm2 set pm2-logrotate:max_size 10M

# Keep a maximum of 30 historical log files
pm2 set pm2-logrotate:retain 30

Conclusion

Deploying Node.js applications with PM2 provides enterprise-grade resilience. By leveraging Cluster Mode to parallelize workloads across all CPU cores, utilizing PM2 Reload to perform zero-downtime code updates, and setting up PM2 Logrotate to protect disk space, you can run high-availability Node.js services.