Report this

What is the reason for this report?

Use PM2 to Setup a Node.js Environment On An Ubuntu VPS

Updated on November 5, 2025
Jim Cassidymkurup

By Jim Cassidy and mkurup

Use PM2 to Setup a Node.js Environment On An Ubuntu VPS

Introduction

Running a Node.js application with node app.js is perfectly acceptable for development. However, this approach is not sustainable for a production environment. If the application crashes, it will not restart on its own. If the server reboots, the application will remain offline until it is manually started. This creates significant reliability problems.

To solve this, a process manager is necessary. PM2 is a production-grade process manager for Node.js applications that provides a simple and effective way to manage and daemonize applications. It keeps applications running indefinitely, reloads them during updates without downtime, and facilitates managing logs, clusters, and startup behavior.

This article will provide a complete guide to installing PM2 on an Ubuntu server, managing a Node.js application, and using its features to build a production-ready setup. We will cover installation, basic commands, security hardening, deployment automation, and advanced monitoring.

Key Takeaways:

  • PM2 provides automatic crash recovery, startup script generation, and process monitoring to keep applications reliably online.

  • PM2’s cluster mode (pm2 start app.js -i max) runs multiple instances across all available cores and load-balances traffic between them, dramatically improving throughput.

  • Using pm2 reload instead of pm2 restart enables code updates without downtime. In cluster mode, PM2 restarts workers one at a time, ensuring at least one instance is always available to handle requests during deployments.

  • To ensure applications restart after server reboots, you must run both pm2 startup (which generates and registers a systemd service) and pm2 save (which saves the current process list). Missing either step means apps won’t auto-restart.

  • The ecosystem.config.js file replaces unwieldy command-line flags with a clean JavaScript configuration. It manages multiple apps, environment variables, cluster settings, and deployment environments in one maintainable location.

  • Production security isn’t a single solution; it requires running as a non-root user, configuring a firewall (UFW), using a reverse proxy (Nginx/Apache for SSL termination and attack mitigation, and protecting secrets through environment variables rather than hardcoding them.

  • PM2 automatically captures application logs, but they grow indefinitely by default. Installing pm2 install pm2-logrotate enables automatic log rotation and retention policies, preventing logs from consuming all available disk space over time.

  • PM2 and Docker solve different problems: PM2 manages Node.js processes while Docker provides environment isolation. The best practice for containerized deployments is using pm2-runtime inside Docker containers to get both container portability and advanced Node.js process management features.

Prerequisites

Before you begin, you will need the following:

  • An Ubuntu server (this guide was tested with 25.04, but any recent version should work).
  • A non-root user with sudo privileges.
  • Node.js and npm installed. Using nvm (Node Version Manager) is strongly recommended as it avoids permission issues. An active LTS release is best.
  • A sample Node.js application. We will create a simple one for testing.
  • A configured firewall (such as UFW) is recommended for production. We will cover basic setup in this guide.

What is PM2?

PM2 is a daemon process manager for Node.js that helps you manage and keep your application online. A “daemon” is a program that runs as a background process, rather than being under the direct control of an interactive user.

PM2’s main features include:

  • Process Monitoring: Automatically restarts applications if they crash or are killed.
  • Startup Scripts: Generates scripts to launch PM2 and your applications on server boot.
  • Cluster Mode: Allows you to run multiple instances of your application, load-balancing traffic across them to take advantage of multi-core CPUs.
  • Log Management: Collects and centralizes logs from all running applications.
  • Zero-Downtime Reloads: Updates applications with new code without losing active connections.

It provides a robust set of features specifically for Node.js applications, making it simpler than configuring systemd from scratch and more feature-rich than simpler tools like forever.

Installing PM2

PM2 is available as an npm package and should be installed globally so it can be accessed anywhere on the system.

A common mistake is to install global npm packages using sudo npm install. This is not recommended as it can cause permission issues and security problems. The best approach is to use a Node Version Manager (nvm), which installs Node.js and npm in your user’s home directory, removing the need for sudo for global packages.

If you have nvm installed, you can install PM2 without sudo:

npm install pm2 -g

If the command above fails with an EACCES (permission denied) error, do not use sudo. Instead, install nvm first, then run the command again in a new terminal session.

Once installed, you can verify the installation by checking its version:

pm2 --version

Creating an Application

First, let’s create a simple “Hello World” application using Express.js, a common Node.js web framework.

  1. Create a new directory for your project and navigate into it:

    mkdir my-app
    cd my-app
    
  2. Initialize a new Node.js project and install Express:

    npm init -y
    npm install express
    
  3. Create a file named app.js with your preferred text editor:

    nano app.js
    
  4. Add the following code to app.js. This creates a basic web server that listens on port 3000.

    // app.js
    const express = require('express');
    const app = express();
    const PORT = 3000;
    
    app.get('/', (req, res) => {
      res.send('Hello from PM2!');
    });
    
    // A simple handler for a graceful shutdown
    process.on('SIGINT', () => {
      console.log('Received SIGINT. Shutting down gracefully.');
      // Close server, database connections, etc.
      process.exit(0);
    });
    
    app.listen(PORT, () => {
      console.log(`Server listening on port ${PORT}`);
    });
    

    Note: We added a SIGINT handler, which will be important for graceful reloads later.

  5. Save and close the file.

Instead of running node app.js, you can now start the application using PM2:

pm2 start app.js

PM2 will start the application in the background and output a table with process information:

[PM2] Spawning PM2 daemon with pm2_home=/home/user/.pm2
[PM2] PM2 successfully daemonized
[PM2] Starting /home/user/my-app/app.js in fork_mode (1 instance)
[PM2] Done.
┌────┬────────────────────┬──────────┬──────┬───────────┬──────────┬──────────┐
│ id │ name               │ mode     │ pid  │ status    │ restart  │ uptime   │
├────┼────────────────────┼──────────┼──────┼───────────┼──────────┼──────────┤
│ 0  │ app                │ fork.    │ 12345│ online    │ 0        │ 0s       │
└────┴────────────────────┴──────────┴──────┴───────────┴──────────┴──────────┘

Your application is now running as a background process. You can test this using curl:

curl http://localhost:3000

You should see the output: Hello from PM2!

Basic PM2 Commands

PM2 provides a simple set of commands for managing your processes. Here are the most common ones:

Command Description
pm2 start <script> Starts the application.
pm2 list Lists all applications managed by PM2.
pm2 stop <id|name> Stops a specific application.
pm2 restart <id|name> Restarts a specific application.
pm2 info <id|name> Displays detailed information about a process.
pm2 logs <id|name> Shows real-time logs for a specific application.
pm2 monit Opens a terminal-based dashboard for monitoring.
pm2 delete <id|name> Stops and removes an application from the PM2 list.

Note on id vs. name: When running in cluster mode, multiple processes will share the same name. Running a command like pm2 stop app will affect all processes in that cluster. To manage a single specific instance, you must use its unique id.

Managing Processes with PM2

Managing a single process is simple, but production environments require more, including scaling across CPU cores and automatic restarts on server boot.

Cluster Mode

On a server with multiple CPU cores, running a single Node.js process is inefficient. The Cluster Mode starts multiple instances (workers) of your application and load-balances incoming traffic between them.

To start app.js in cluster mode, use the -i (instances) flag. You can specify a number, or use max to automatically detect the number of available CPU cores:

# Start the app using all available cores
pm2 start app.js -i max

If you run pm2 list, you will now see multiple instances of your app running, each with a separate pid but sharing the same name.

Zero-Downtime Reloads

When you need to update your application’s code, pm2 restart is one option, but it stops and then starts the application, which causes a brief period of downtime.

A better method is pm2 reload. This command is a key feature of cluster mode and provides a graceful, zero-downtime update. It works by restarting each worker one by one, waiting for the new worker to be online before taking the old one down. This ensures that there is always at least one worker available to handle requests.

pm2 reload app

This process works by sending a SIGINT signal to the old process. For a truly graceful shutdown, your Node.js application should listen for this signal (like process.on('SIGINT', ...) as shown in our example). This allows it to finish in-progress requests and clean up resources before exiting.

Important: If you use pm2 reload on an application running in fork mode (a single instance), it will simply fall back to the behavior of pm2 restart, which will cause downtime.

Saving the Process List for Server Reboots

A critical step is ensuring your applications restart when the server reboots. PM2 can generate a startup script that does this.

  1. Run the startup command:

    pm2 startup
    
  2. PM2 will output a command specific to your system (usually using systemd on modern Ubuntu). You must copy and run this command:

    [PM2] To setup the Startup Script, copy/paste the following command:
    sudo env PATH=$PATH:/home/sammy/.nvm/versions/node/v22.21.0/bin /home/sammy/.nvm/versions/node/v22.21.0/lib/node_modules/pm2/bin/pm2 startup systemd -u sammy --hp /home/sammy
    

    (Your path will vary depending on your Node.js version and username.)

  3. Run the command that PM2 provides. This creates a systemd unit that will launch PM2 on boot.

  4. Finally, save your current process list. PM2 will remember these applications and restart them when it launches:

    pm2 save
    

Now, your applications will automatically restart after a server reboot.

Configuring PM2

Managing applications with command-line flags becomes difficult as options grow. PM2’s preferred method is an ecosystem file. This is a JavaScript configuration file, typically named ecosystem.config.js, that defines all settings for one or more applications.

  1. Generate a template file:

    pm2 ecosystem
    
  2. This creates a template ecosystem.config.js file. Here’s an example configured for our application:

    // ecosystem.config.js
    module.exports = {
      apps : [{
        name   : "my-app",
        script : "./app.js",
        instances: "max",
        exec_mode: "cluster",
        watch: false, // Disable watch mode for production
        env_production: {
           NODE_ENV: "production",
           PORT: 3000
        },
        env_development: {
           NODE_ENV: "development",
           PORT: 3001
        }
      }]
    }
    

This file defines an application named “my-app”, specifies its script, and sets it to run in cluster mode using all available CPU cores. It also defines different environment variables for production and development.

  • To start this application, simply run:

    pm2 start ecosystem.config.js
    
  • To start it specifically using the production environment:

    pm2 start ecosystem.config.js --env production
    
  • If you change environment variables in the file for an already-running app, you must use the --update-env flag with reload or restart:

    pm2 reload ecosystem.config.js --env production --update-env
    

Monitoring and Debugging

PM2 provides several tools for monitoring and debugging your application, from built-in command-line tools to a sophisticated cloud-based dashboard.

Command-Line Monitoring

If your application doesn’t work as intended, you can use the following commands to debug:

  • pm2 list: This is your first stop. Check the status column. If it’s errored, the app is crashing. Check the restart count. A high, climbing number indicates a crash loop.
  • pm2 logs app: This command tails the application’s logs in real-time. This is the most effective way to see application errors. PM2 splits logs into standard output (app-out.log) and standard error (app-error.log).
  • pm2 monit: This launches a terminal-based dashboard showing real-time CPU and memory usage for each process.
  • pm2 info app: This provides a detailed overview, including the application’s file paths, environment variables, and log file locations.

By default, PM2 log files will grow indefinitely. For a production server, this can eventually fill up your disk space. PM2 provides a module for managing log rotation. pm2-logrotate automatically “rotates” the log files when they reach a certain size or time. It renames the large log file (e.g., to app-out-1.log) and creates a new, empty one for the application to continue writing to. It then enforces a retention policy by automatically deleting the oldest log files, ensuring your server’s disk never fills up. You can install it with: pm2 install pm2-logrotate.

Cloud-Based Monitoring (PM2 Plus)

For more advanced, cross-server monitoring, PM2 offers PM2 Plus (also known as Keymetrics). This is a cloud-based dashboard that provides:

  • A centralized web interface for all your servers.
  • Real-time metrics on CPU, memory, and application performance.
  • Issue and exception tracking.
  • Transaction tracing to identify performance bottlenecks.
  • Alerting via Slack, email, and other services.

To connect your application, you run a simple command:

pm2 link <secret_key> <public_key>

This links your local PM2 daemon to the web dashboard, providing a comprehensive view of your application’s health without requiring additional server setup.

Security Considerations and Best Practices

When running a production server, security is a primary concern. A single-line of defense, such as a simple password, is not enough. A multi-layered approach is the best practice. By implementing multiple, overlapping layers of security, you create a robust system where an attacker who breaches one layer is immediately confronted by another. For a PM2 and Node.js setup, this means securing the network, the server, the application gateway, and the application code itself.

Configuring a Basic Firewall (UFW)

The ufw (Uncomplicated Firewall) utility is a standard, easy-to-use firewall for Ubuntu. You should only allow traffic to the specific ports you need.

  1. Allow SSH: This is essential so you do not lock yourself out of the server.

    sudo ufw allow OpenSSH
    
  2. Allow HTTP/HTTPS: If you are using a reverse proxy like Nginx (recommended), you should allow traffic on ports 80 and 443.

    sudo ufw allow 'Nginx Full'
    
  3. Deny Other Ports: Your Node.js application port (e.g., 3000) should not be publicly accessible. The firewall should block it by default, and only Nginx (running on the same server) should be able to access it.

  4. Enable UFW:

    sudo ufw enable
    

This configuration ensures that the public can only access your web server, which then securely forwards requests to your PM2-managed application.

Running as a Non-Root User

A fundamental security principle is the principle of least privilege. This means any application or user should only have the bare minimum permissions necessary to perform its function.

You should never run your Node.js application as the root user. If an attacker found a vulnerability in your application, they could gain root access to your entire server.

Instead, create a dedicated non-privileged user for your application. When you run the pm2 startup command, use the -u flag to specify this user, as shown in the Saving the Process List section. This user should not have sudo permissions. This way, even if your application is compromised, the attacker’s access is limited to that user’s home directory and permissions.

Using a Reverse Proxy for Security

A reverse proxy, such as Nginx or Apache, acts as a “front door” for your application. Public users connect to the proxy, and the proxy forwards the request to your Node.js application, which is running locally on a port like 3000. This setup provides several key security benefits:

  • Abstraction: The public never directly accesses your Node.js application. This hides details about your application stack and prevents direct attacks against the Node.js process.
  • SSL Termination: The reverse proxy can handle all HTTPS and SSL certificate complexity. This encrypts all traffic between the user and your server. Your Node.js application can run on a simple HTTP port locally, simplifying your code.
  • Attack Mitigation: A robust web server like Nginx is built to handle the public internet. It can be configured for rate limiting (to prevent brute-force attacks), blocking malicious IPs, and mitigating common web vulnerabilities.
  • Static Content: The proxy can efficiently serve static files (CSS, JavaScript, images), reducing the load on your Node.js application, which is better saved for dynamic requests.

Protecting Environment Variables

Your application will inevitably need “secrets” like database passwords, API keys, or session tokens. These must never be hardcoded into your application’s source code (e.g., const DB_PASS = "mypassword123"). If your code is in a Git repository, you will have permanently leaked that secret.

These secrets should be passed to your application using environment variables. PM2’s ecosystem.config.js file allows you to define these in an env_production block. However, if you commit this file to source control, you still have the same problem.

Here are the two recommended methods:

  • Using a .env File: Create a file named .env in your project’s root directory. Place your secrets here:

    DB_HOST=localhost
    DB_PASS=mypassword123
    API_KEY=supersecretkey
    

    Add .env to your .gitignore file to ensure it is never committed. Then, in your application, use a package like dotenv (npm install dotenv) to load these variables.

  • Injecting During Deployment: This is a more secure method for CI/CD pipelines. Your secrets are stored securely in your CI/CD tool (e.g., GitHub Actions Secrets, GitLab CI/CD Variables). During the deployment step, the pipeline securely logs into your server and passes the variables directly to the PM2 command:

    API_KEY=$CI_SECRET_KEY DB_PASS=$CI_DB_PASS pm2 reload ecosystem.config.js --env production
    

    This way, the secrets never touch a file on the server, existing only as process environment variables.

Disable Watch Mode in Production

PM2 has a watch mode that automatically restarts your application when it detects file changes. This is a convenient feature for development. In production, it is a liability.

You must disable watch mode because:

  • Unintended Restarts: Many things can write files to your application directory, including log files or temporary cache files. This can trigger an unwanted application restart.

  • Resource Intensive: Continuously watching the filesystem consumes unnecessary CPU and I/O resources.

  • Not a Deployment Strategy: This is not a proper way to deploy code. A real deployment should be a deliberate, controlled event, usually involving a CI/CD pipeline, dependency installation, and a graceful pm2 reload.

Explicitly set watch: false in your ecosystem file to ensure it is disabled:

// ecosystem.config.js
module.exports = {
  apps : [{
    name   : "my-app",
    script : "./app.js",
    watch: false, // Explicitly disable for production
    ...
  }]
}

Common Issues and Troubleshooting

When working with PM2 in a production environment, you may encounter a few common issues. This section provides detailed explanations and solutions for them.

Application Status is errored or in a Crash Loop

  • Problem: You run pm2 list and see your application’s status is errored instead of online. The restart count for that process may also be very high and climbing.

  • Cause: errored status means PM2 is successfully starting your application, but the application is crashing almost immediately. PM2, doing its job, tries to restart it, leading to a “crash loop.” This is almost always caused by an error in your application code, not PM2.

  • Solution: You must read the application’s logs to find the error.

    1. Use the pm2 logs command to view the error in real-time. Replace app with your application’s name or ID:

      pm2 logs app --lines 100
      
    2. Look for common startup errors, such as:

      • ReferenceError or TypeError: A variable is not defined or a function is misused.
      • Error: EADDRINUSE :::3000: The port your app is trying to use (e.g., 3000) is already in use by another service.
      • Database connection errors: The application cannot reach the database due to wrong credentials or a down service.
      • Module not found: You ran npm install but forgot a required package.
    3. You can also find the full log file path by running pm2 info app and checking the error log path.

pm2: command not found After Server Reboot

  • Problem: You reboot the server, and your applications are not running. When you log in and type pm2 list, you get a bash: pm2: command not found error.

  • Cause: This almost always happens when you have installed Node.js and PM2 using nvm (Node Version Manager). The pm2 startup command creates a systemd service that runs on boot, but the system’s root user does not know where your nvm-installed pm2 executable is. The PATH environment variable is not set correctly for the boot-time service.

  • Solution: You must run the pm2 startup command and copy the exact command it outputs. This output includes the necessary PATH environment variable.

    1. If you have a broken startup script, remove it first:

      pm2 unstartup
      
    2. Run the startup command again:

      pm2 startup
      
    3. Carefully copy the full command PM2 gives you. It will look something like this, with the env PATH=$PATH... part being the most important:

      sudo env PATH=$PATH:/home/sammy/.nvm/versions/node/v22.21.0/bin /home/sammy/.nvm/versions/node/v22.21.0/lib/node_modules/pm2/bin/pm2 startup systemd -u sammy --hp /home/sammy
      
    4. Run this sudo command. This correctly registers the systemd service with the correct path to pm2.

    5. Finally, run pm2 save to store your current process list.

Applications Do Not Restart on Reboot (but pm2 command works)

  • Problem: You reboot the server. When you log in, pm2 list works, but the list of applications is empty. Your apps are not running.

  • Cause: PM2’s startup process is two-fold:

    1. pm2 startup creates a systemd service that launches the PM2 daemon on boot.
    2. pm2 save saves your current list of running processes to a file on disk (e.g., ~/.pm2/dump.pm2). When PM2 starts on boot, it loads this file to know what to run. You most likely forgot to run pm2 save after starting your applications.
  • Solution:

    1. Start all your applications as you normally would:

      pm2 start ecosystem.config.js --env production
      
    2. Verify they are online with pm2 list.

    3. Run pm2 save to write this process list to disk.

      pm2 save
      
    4. You can test this by running sudo reboot and checking pm2 list after the server is back online.

Deployed Code Changes Are Not Reflected

  • Problem: You pulled new code from your Git repository and ran npm install, but your application is still serving the old code.

  • Cause: PM2 does not automatically detect file changes unless you explicitly enable watch mode. Watch mode is not recommended for production as it is resource-intensive and can lead to unintended restarts. You must manually tell PM2 to update the running processes with the new code.

  • Solution: Do not use pm2 restart. This command causes downtime. Instead, use pm2 reload:

    pm2 reload ecosystem.config.js --env production
    

    Or, if you are not using an ecosystem file:

    pm2 reload app
    

    The reload command performs a zero-downtime reload in cluster mode. It restarts each worker instance one by one, ensuring at least one worker is always available to handle traffic.

High CPU (100%) or Memory Usage

  • Problem: Your server becomes slow, and checking with top or htop shows a Node.js or PM2 process at 100% CPU, or its memory usage is climbing steadily.

  • Cause:

    • 100% CPU: This is often a symptom of a crash loop, as described in the first issue. The application crashes and restarts so quickly that it consumes a full CPU core.
    • High Memory: This typically indicates a memory leak in your Node.js application. Your code is creating objects, adding listeners, or holding references that the garbage collector cannot clean up, causing memory to grow until the process crashes (and is restarted by PM2).
  • Solution:

    1. Run pm2 monit to get a real-time dashboard of CPU and memory usage for each process.
    2. If CPU is at 100%, check the logs immediately with pm2 logs app. A fast-scrolling error log confirms a crash loop.
    3. If memory is climbing steadily, this is a code-level memory leak. PM2 is keeping your app alive, but it cannot fix the leak. You will need to use Node.js debugging tools like the built-in --inspect flag or external services to profile your application’s memory heap and find the source of the leak.

Comparing PM2 with Other Tools

Choosing the right tool to run your Node.js application in production is a critical decision. PM2 is a powerful and popular choice, but it is not the only option. It exists in an ecosystem of tools that includes native operating system services, simpler process runners, and full-blown containerization platforms.

The best choice depends on your project’s specific needs, scale, and complexity. This section provides a detailed comparison of PM2 with its main alternatives: systemd, forever, and Docker.

PM2 vs. systemd

systemd is the default init system and service manager for most modern Linux distributions, including Ubuntu. It is responsible for starting, stopping, and managing all system services (like the SSH server, network, and database).

  • Pros:

    • Built-in and Robust: As a native part of the OS, systemd is extremely stable, efficient, and has virtually zero performance overhead.
    • Language-Agnostic: It can manage any executable, not just Node.js applications.
    • Service Dependencies: You can configure systemd to start your Node.js application only after the database service (like PostgreSQL or MySQL) is online.
  • Cons:

    • Verbose Configuration: Setting up a service requires writing a .service unit file. This is more complex and less intuitive than a PM2 command or ecosystem file.
    • Not “Node-Aware”: systemd sees your application as just another process. It has no built-in understanding of Node.js. Critical features like cluster mode (to utilize all CPU cores) and zero-downtime reloads must be manually scripted, which is a significant and complex task.

For simple applications on a single server, systemd is a viable, lightweight option. However, it lacks the Node.js-specific features that PM2 provides out of the box, making PM2 a more practical and feature-rich choice for Node.js developers.

PM2 vs. forever

forever is one of the original process managers for Node.js. Like PM2, its basic purpose is to keep a Node.js script running.

  • Pros:

    • Simple and Lightweight: It is very easy to use for its one task: restarting a script if it crashes.
  • Cons:

    • Lacks Production Features: forever has been largely superseded by PM2 because it is missing nearly every key feature needed for a modern production environment. It has:
      • No built-in cluster mode.
      • No zero-downtime reload.
      • No startup script generation (you must manually hook it into crontab or systemd).
      • No ecosystem configuration file for managing multiple apps.

forever is a tool from a previous generation. For any serious production application, PM2 is the superior choice.

PM2 vs. Docker

This is the most common point of comparison, and it’s important to understand they solve different problems. Docker is a containerization platform, not just a process manager. It packages your application, its dependencies, its runtime (Node.js), and parts of the operating system into a single, isolated “container.”

  • Pros:

    • Full Environment Isolation: The primary benefit. A container runs identically on your laptop, a testing server, and in production, eliminating “it works on my machine” problems.
    • Portability and Scalability: Containers can be easily moved and scaled across many servers using an orchestrator like Kubernetes.
  • Cons:

    • Higher Complexity: Docker introduces a new layer of abstraction, including virtual networks, storage volumes, and image management.
    • Process Management: By default, Docker only manages the container itself. If the node app.js command (PID 1) inside the container crashes, the container stops and restarts. It does not provide built-in clustering or graceful reloads.

When to Choose PM2 vs. Docker

  • Choose PM2 when: Your goal is to run a Node.js application on a traditional Ubuntu server (a virtual machine or bare metal). It provides the simplest and fastest path to a production-ready setup with process monitoring, clustering, and log management. It is ideal for monolithic applications, small microservices, or any project where the complexity of container orchestration is not (yet) required.

  • Choose Docker when: Your primary need is environment consistency and portability. If you have a complex microservice architecture, need to ensure development and production environments are identical, or plan to deploy to a cloud platform that uses Kubernetes, Docker is the right choice.

  • The “Both” Strategy (Recommended for Docker Users): A very common and powerful pattern is to use PM2 inside a Docker container Instead of running node app.js as your container’s CMD, you use pm2-runtime ecosystem.config.js. This gives you the best of both worlds. Docker provides the environment isolation and portability. PM2 manages the Node.js process inside the container, giving you cluster mode (to use all CPU cores allocated to the container) and zero-downtime reloads. The pm2-runtime command is specifically designed to run PM2 in the foreground, which is what Docker’s container lifecycle requires.

Performance Benchmarks and Overhead

While exact performance benchmarks are application-specific, we can compare the general overhead and impact of each tool:

  • systemd: Has almost no measurable overhead, as it is a native OS component.
  • forever: Has minimal overhead, as it is a simple Node.js script.
  • PM2: The PM2 daemon itself has a very small, negligible memory and CPU footprint. Its most significant performance impact is positive. The cluster mode feature allows your single-threaded Node.js application to utilize all CPU cores on your server, dramatically improving application throughput. This benefit far outweighs the small overhead of the daemon.
  • Docker: Introduces a small but measurable overhead from the containerization layer (virtualized networking, filesystem). This is minimal on modern Linux systems but is still greater than running a process directly on the host.

Comparison Summary Table

Feature PM2 systemd Docker forever
Primary Use Node.js Process Management System-wide Service Management Environment Containerization. Simple Process Runner
Cluster Mode Yes (Built-in) No (Requires manual scripting) No (Manages container, not process) No
Zero-Downtime Reload Yes (Built-in) No (Requires manual scripting) No (Restarts entire container) No
Startup Script Yes (Generates systemd script) Yes (Is the startup system) No (Managed by orchestrator) No (Requires manual setup)
Environment Isolation No No Yes (Full OS-level isolation) No
“Node-Aware” Yes No No Yes
Best For Node.js apps on a VM System-level services Portability & Microservices Very simple scripts

CI/CD and Deployment Automation

PM2 is easily integrated into an automated deployment pipeline (CI/CD). Instead of manually running git pull and pm2 reload on your server, you can use tools like GitHub Actions or Jenkins.

A typical deployment script in your CI/CD pipeline might look like this:

#!/bin/bash
# deploy.sh

# 1. Navigate to the application directory
cd /var/www/my-app

# 2. Pull the latest code from the main branch
git pull origin main

# 3. Install or update dependencies
npm install --production

# 4. Reload the application with PM2
# This will perform a zero-downtime reload
pm2 reload ecosystem.config.js --env production

In your GitHub Actions workflow, you would have a “Deploy” step that uses SSH to run this deploy.sh script on your server after your tests have passed. The pm2 reload command is the key, as it updates the application without any downtime for your users.

FAQs

1. What is PM2 used for in Node.js?

PM2 is a production process manager for Node.js. Its main purpose is to run your Node.js application in a stable and reliable way, solving the problems that node app.js does not.

It wraps your application and provides a set of essential features:

  • Process Monitoring: It automatically restarts your application if it crashes due to an error.
  • Startup Scripts: It ensures your application restarts automatically if the entire server reboots.
  • Cluster Mode: It allows your application to use all available CPU cores, dramatically improving performance.
  • Log Management: It captures all your application’s logs (console.log, console.error) and saves them to files.
  • Zero-Downtime Reloads: It lets you update your application’s code without any downtime for your users.

2. How do I auto-start my Node.js app on Ubuntu reboot?

You use a two-step process that involves the startup and save commands.

  1. pm2 startup: First, you run this command. PM2 will detect your system’s init system (like systemd on Ubuntu) and give you a sudo command to run. You must copy and paste this command. This registers the PM2 daemon itself as a system service.
  2. pm2 save: Once the daemon is set to start on boot, you must tell it which applications to run. This command saves your currently running process list to a file.

After you do both, when your server reboots, systemd will start PM2, and PM2 will read its saved list and relaunch all your applications.

3. How does PM2 compare to Forever.js or Docker?

  • vs. Forever.js: forever is a simpler, older tool that only restarts a script if it crashes. PM2 is a complete replacement for forever, adding critical production features that forever lacks, such as a built-in cluster mode, zero-downtime reloads, and automatic startup script generation.
  • vs. Docker: They solve different problems. Docker is a containerization platform that isolates your app and its entire environment (Node.js, libraries, etc.). PM2 is a process manager that runs inside an environment. The best practice is often to use them together: you run PM2 (using pm2-runtime) inside a Docker container to get both environment isolation (from Docker) and Node.js process management (from PM2).

4. Can I monitor my Node.js apps remotely with PM2?

Yes. PM2 offers a cloud-based service called PM2 Plus (also known as Keymetrics).

You run a pm2 link command on your server to connect it to the PM2 Plus web dashboard. This gives you a centralized, remote interface where you can monitor real-time CPU and memory usage, view logs, and set up alerts for crashes or performance issues from any machine. This is separate from the built-in CLI commands like pm2 monit, which only work on the server itself.

5. How do I use PM2 cluster mode for better performance?

You use PM2’s cluster mode to run multiple instances of your application, which is the best way to improve its performance on a multi-core server.

Why: A standard Node.js application is single-threaded, meaning it only uses one CPU core, even if your server has 8 or 16. The other cores sit idle. How: PM2’s cluster mode starts one instance of your app on each CPU core and automatically load-balances incoming traffic between them.

You can enable it in two ways:

  1. Via the CLI: Use the -i max flag to automatically detect and use all available cores.

    pm2 start app.js -i max
    
  2. Via an Ecosystem File (Recommended): In your ecosystem.config.js, set these two properties:

    ...
    instances: "max",
    exec_mode: "cluster",
    ...
    

Conclusion

PM2 provides a simple and feature-rich solution for running Node.js applications in a production environment. It solves the fundamental problems of process monitoring, restarts, and scaling that are not addressed by simply running node app.js.

By using PM2’s cluster mode and startup scripts, you can ensure your application is resilient and utilizes your server’s hardware efficiently. When combined with an ecosystem configuration file, a reverse proxy like Nginx, and automated deployment scripts, PM2 forms the foundation of a robust and maintainable production setup.

For more Node.js-related tutorials, check out the following articles:

Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.

Learn more about our products

About the author(s)

Jim Cassidy
Jim Cassidy
Author
mkurup
mkurup
Editor
Category:

Still looking for an answer?

Was this helpful?


This textbox defaults to using Markdown to format your answer.

You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!

What does the “usermod -aG sudo &ltusername>” command do as I’m getting a error: bash: syntax error near unexpected token `newline’

Thanks

@sharry: There’s a typo – I’ve updated it. Try now.

@sharry. This command will add the user to the group of users who are allowed to use sudo to run root commands. The ‘a’ is important because it appends the group to the list of groups your user belongs to - otherwise, your user would l lose membership in other groups.

@Jim thanks for explanation.

Very nice tutorial, thanks for this!

PS: The links to the Git book and pm2 repository both have a comma at the end of the url causing 404 errors

When I input the following

 sudo npm install pm2 -g

I get an error message

npm http GET https://registry.npmjs.org/configurable/0.0.1

npm http GET https://registry.npmjs.org/escape-regexp/0.0.1 npm http 304 https://registry.npmjs.org/bindings

usage@0.3.9 install /usr/local/lib/node_modules/pm2/node_modules/usage node-gyp rebuild

gyp WARN EACCES user “root” does not have permission to access the dev dir “/home/nodeuser/.node-gyp/0.10.24” gyp WARN EACCES attempting to reinstall using temporary dev dir “/home/nodeuser/tmp/.node-gyp”

How do I preceed from here?

Hi - those look like warnings, not errors. You can see in the last line that the script attempts to reinstall using the temporary dev dir.

I installed pm2 on my droplet to see if I saw the same thing you did - I did. The good news it that the install succeeded despite the warnings.

One way to find out if the install went well even with warnings is to go to the command line and type pm2. If you see a help screen - you are good to go! I am pretty sure you will find that all is well.

Please find below the output from a successful instead - you will notice the same warnings. Good luck, my friend.

gyp WARN EACCES user “root” does not have permission to access the dev dir “/root/.node-gyp/0.10.24” gyp WARN EACCES attempting to reinstall using temporary dev dir “/usr/lib/node_modules/pm2/node_modules/usage/.node-gyp” gyp http GET http://nodejs.org/dist/v0.10.24/node-v0.10.24.tar.gz gyp http 200 http://nodejs.org/dist/v0.10.24/node-v0.10.24.tar.gz make: Entering directory /usr/lib/node_modules/pm2/node_modules/usage/build' CXX(target) Release/obj.target/sysinfo/src/binding.o SOLINK_MODULE(target) Release/obj.target/sysinfo.node SOLINK_MODULE(target) Release/obj.target/sysinfo.node: Finished COPY Release/sysinfo.node make: Leaving directory /usr/lib/node_modules/pm2/node_modules/usage/build’ npm http GET https://registry.npmjs.org/keypress npm http 200 https://registry.npmjs.org/keypress /usr/bin/pm2 -> /usr/lib/node_modules/pm2/bin/pm2 pm2@0.7.1 /usr/lib/node_modules/pm2 ├── debug@0.7.4 ├── commander@2.1.0 ├── eventemitter2@0.4.13 ├── pm2-interface@0.1.0 ├── watch@0.8.0 ├── colors@0.6.2 ├── cron@1.0.1 ├── async@0.2.9 ├── cli-table@0.2.0 (colors@0.3.0) ├── axon@1.0.0 (escape-regexp@0.0.1, configurable@0.0.1) ├── pm2-multimeter@0.1.2 (charm@0.1.2) ├── coffee-script@1.6.3 ├── axon-rpc@0.0.2 (commander@1.0.5) └── usage@0.3.9 (bindings@1.1.1)

Sorry - I investigated further to see what the problem might be with sudo npm install pm2 -g. I often double check - I started from a fresh Droplet. I did find a problem.

The key is to remove /home/nodeuser/tmp and recreate it. Then try sudo npm install pm2 -g again. That worked for me. Don’t know why this happens. Good luck.

removing /home/noduser/tmp and recreating it worked a charm! :) Thanks a million!

Hi On running the command sudo env PATH=$PATH:/usr/local/bin pm2 startup

I am requested to supply a ‘platform’ argument

(error: missing required argument `platform’)

What would that be? Thanks

Creative CommonsThis work is licensed under a Creative Commons Attribution-NonCommercial- ShareAlike 4.0 International License.
Join the Tech Talk
Success! Thank you! Please check your email for further details.

Please complete your information!

The developer cloud

Scale up as you grow — whether you're running one virtual machine or ten thousand.

Get started for free

Sign up and get $200 in credit for your first 60 days with DigitalOcean.*

*This promotional offer applies to new accounts only.