How to run Node.js Applications with PM2?

Youth DreamYouth Dream
9 min read

What is PM2?

PM2 is a daemon process manager that will help you manage and keep your application online. Getting started with PM2 is straightforward, it is offered as a simple and intuitive CLI, installable via NPM.

This article will introduce you to the key features of PM2 and help you leverage it for deploying, overseeing, and scaling your Node.js applications effectively in a production environment.

How to install PM2?

The latest PM2 version is installable with NPM or Yarn:

$ npm install pm2@latest -g  
# or  
$ yarn global add pm2

After installation, check version.

$ pm2 --version

Start your application in production with PM2.

The simplest way to start, daemonize and monitor your application is by using this command line:

$ pm2 start server.js

Or start any other application easily:

$ pm2 start bashscript.sh
$ pm2 start python-app.py --watch
$ pm2 start binary-file -- --port 1520

Some options you can pass to the CLI:

# Specify an app name
--name <app_name>

# Watch and Restart app when files change
--watch

# Set memory threshold for app reload
--max-memory-restart <200MB>

# Specify log file
--log <log_path>

# Pass extra arguments to the script
-- arg1 arg2 arg3

# Delay between automatic restarts
--restart-delay <delay in ms>

# Prefix logs with time
--time

# Do not auto restart app
--no-autorestart

# Specify cron for forced restart
--cron <cron_pattern>

# Attach to application log
--no-daemon

As you can see many options are available to manage your application with PM2. You will discover them depending on your use case.

Check status, logs, metrics

List managed applications

With your application in motion, PM2 offers a suite of subcommands — list, show, and monit—to help you monitor its performance. To get an overview of all active applications on your server, use:

$ pm2 list

You should observe the following output:

$ pm2 list

You should observe the following output:

┌─────┬─────────────┬─────────────┬─────────┬─────────┬──────────┬────────┬──────┬───────────┬──────────┬──────────┬──────────┬──────────┐
│ id  │ name        │ namespace   │ version │ mode    │ pid      │ uptime │ ↺    │ status    │ cpu      │ mem      │ user     │ watching │
├─────┼─────────────┼─────────────┼─────────┼─────────┼──────────┼────────┼──────┼───────────┼──────────┼──────────┼──────────┼──────────┤
│ 0   │ --appname   │ default     │ 1.0.0   │ fork    │ 18529    │ 3m     │ 0    │ online    │ 0%       │ 53.3mb   │ ayo      │ disabled │
└─────┴─────────────┴─────────────┴─────────┴─────────┴──────────┴────────┴──────┴───────────┴──────────┴──────────┴──────────┴──────────┘

This command provides a snapshot of each application’s status, uptime, memory usage, and more. If you have multiple applications running, you can sort them based on specific metrics:

$ pm2 list --sort [name|id|pid|memory|cpu|status|uptime][:asc|desc]

As in:

$ pm2 list --sort memory:desc

For a more detailed look at a specific application, use the pm2 show command followed by the application's name or id:

$ pm2 show appname

For a live dashboard displaying metrics, metadata, and application logs, use:

$ pm2 monit

Display logs

To display logs in realtime:

$ pm2 logs

To dig in older logs:

$ pm2 logs --lines 200

Fine-tuning auto-restart strategies

PM2 employs an ecosystem.config.js file to consolidate configuration settings for one or more applications. To generate this file in your project directory, use:

$ pm2 init simple

Upon execution, you should see:

File /home/project/appname/ecosystem.config.js generated

Edit the ecosystem.config.js file and update the name and script fields to match your application:

module.exports = {
  apps: [{
    name: 'appname',
    script: './server.js',
  }]
}

With this configuration, you can manage all declared applications:

$ pm2 start ecosystem.config.js
$ pm2 restart ecosystem.config.js
$ pm2 stop ecosystem.config.js

Let’s now delve into advanced restart strategies:

Restarting based on memory usage

module.exports = {
  apps: [{
    name: 'appname',
    script: './server.js',
    max_memory_restart: '1G',
  }]
}

This restarts the appname application if memory usage surpasses 1 Gigabyte. You can also configure the max_memory_restart option in Kilobyte (K), and Megabyte (M).

Restarting based on a CRON schedule

module.exports = {
  apps: [{
    name: 'appname',
    script: './server.js',
    cron_restart: '0 */24 * * *',
  }]
}

To learn and test the cron syntax, consider using the crontab guru editor.

Delayed restarts

You can introduce a delay before PM2 restarts an application using the restart_delay option:

module.exports = {
  apps: [{
    name: 'appname',
    script: './server.js',
    restart_delay: 5000 // wait for five seconds before restarting
  }]
}

Exponential backoff restart delay

Instead of setting a fixed delay before restarting the application, you can use the exp_backoff_restart_delay option to to raise the time between restarts up to 15 seconds incrementally. The initial delay time set through this option will be multiplied by 1.5 after each restart attempt.

module.exports = {
  apps: [{
    name: 'appname',
    script: './server.js',
    exp_backoff_restart_delay: 100 // 100ms
  }]
}

With the above in place, the first restart attempt will be delayed by 100ms, second restart 150ms, then 225ms, 337.5ms, 506.25ms, and so on. This delay is reset to 0ms if the application remains online for over 30 seconds.

Setting a maximum restart limit

PM2 provides a max_restarts option for configuring the maximum number of unstable restarts before the application is considered to have encountered an unrecoverable error. This lets you prevent your application from constantly dying and restarting, which may waste resources.

You can also specify an unstable restart through the min_uptime option. This allows you to specify the amount of time before your application is considered "online":

module.exports = {
  apps: [{
    name: 'appname',
    script: './server.js',
    max_restarts: 16,
    min_uptime: 5000, // 5 seconds
  }]
}

Disabling automatic restarts

To turn off automatic restarts entirely, set the autorestart option to false:

module.exports = {
  apps: [{
    name: 'appname',
    script: './server.js',
    autorestart: false,
  }]
}

Now, go ahead and integrate these strategies into your ecosystem.config.js:

module.exports = {
  apps: [
    {
      name: 'appname',
      script: './server.js',
      exp_backoff_restart_delay: 100,
      max_memory_restart: '1G',
      max_restarts: 10,
      min_uptime: 2000,
    },
  ],
};

Remove the existing appname instance:

$ pm2 delete appname

Then, relaunch it using the configuration file:

$ pm2 start ecosystem.config.js

Sample ecosystem.config.js file:

module.exports = {
  apps : [{
    name: "app",
    script: "./app.js",
    env: {
      NODE_ENV: "development",
    },
    env_production: {
      NODE_ENV: "production",
    }
  }, {
     name: 'worker',
     script: 'worker.js'
  }]
}

Launching applications on system startup

In the last section, we explored methods to keep your application resilient against unexpected crashes. In this segment, we’ll delve into how to set up a Node.js application to autostart after system reboots or crashes using PM2 and Systemd on Linux. Although PM2 is compatible with various init systems, including upstart, launchd, openrc, rcd, and systemv, this guide focuses on Systemd.

Begin by generating an init script for your system. This script will initiate the pm2 daemon during system startup, which in turn will launch any saved applications:

$ pm2 startup systemd

To ensure that PM2 automatically starts any saved processes upon system boot, save your PM2 process list:

$ pm2 save

Managing your application logs

PM2 automatically archives logs generated by your application in the $HOME/.pm2/logs directory. Logs directed to the standard output are stored in the <app_name>-out.log file, while logs directed to the standard error are stored in the <app_name>-error.log file.

ls ~/.pm2/logs
appname-error.log  appname-out.log

Within the ecosystem.config.js file, you can define the error_file and out_file options to specify custom locations for the application's error and output log files:

module.exports = {
  apps: [
    {
      name: 'appname',
      script: './server.js',
      exp_backoff_restart_delay: 100,
      max_memory_restart: '1G',
      max_restarts: 10,
      min_uptime: 2000,
      out_file: '<custom_path>', // use /dev/null to disable
      error_file: '<custom_path>', // use /dev/null to disable
    },
  ],
};

Enabling log rotation

To ensure that your application log files are rotated before they get too large, install the pm2-logrotate module as shown below:

$ pm2 install pm2-logrotate

Afterward, when you run pm2 list, you should see a new "Module" section like so:

┌─────┬──────────────────┬─────────────┬─────────┬─────────┬──────────┬────────┬──────┬───────────┬──────────┬──────────┬──────────┬──────────┐
│ id  │ name             │ namespace   │ version │ mode    │ pid      │ uptime │ ↺    │ status    │ cpu      │ mem      │ user     │ watching │
├─────┼──────────────────┼─────────────┼─────────┼─────────┼──────────┼────────┼──────┼───────────┼──────────┼──────────┼──────────┼──────────┤
│ 0   │ appname          │ default     │ 1.0.0   │ fork    │ 9408     │ 3m     │ 0    │ online    │ 0%       │ 50.2mb   │ ayo      │ disabled │
└─────┴──────────────────┴─────────────┴─────────┴─────────┴──────────┴────────┴──────┴───────────┴──────────┴──────────┴──────────┴──────────┘
Module
┌────┬──────────────────────────────┬───────────────┬──────────┬──────────┬──────┬──────────┬──────────┬──────────┐
│ id │ module                       │ version       │ pid      │ status   │ ↺    │ cpu      │ mem      │ user     │
├────┼──────────────────────────────┼───────────────┼──────────┼──────────┼──────┼──────────┼──────────┼──────────┤
│ 1  │ pm2-logrotate                │ 2.7.0         │ 12068    │ online   │ 0    │ 0%       │ 21.4mb   │ ayo      │
└────┴──────────────────────────────┴───────────────┴──────────┴──────────┴──────┴──────────┴──────────┴──────────┘

By default, the pm2-logrotate module rotates log files once they exceed 10 megabytes. It retains up to 30 rotated files and deletes older ones. Backup files are not compressed by default. You can adjust these settings and more by referring to the module's documentation, or you use logrotate to handle log file rotation instead.

The pm2 logs command may come in handy in development for displaying incoming application log entries in real time:

$ pm2 logs appname

To explore all available options for the logs command and customize its output, use:

$ pm2 logs --help

Deploying your application to production

PM2 provides an integrated deployment system to facilitate deploying your application to one or multiple remote servers. To set this up, modify your ecosystem.config.js file as follows:

module.exports = {
  apps: [],
  deploy: {
    production: {
      user: '<your_remote_server_username>',
      host: [<your_remote_server_ip>],
      ref: 'origin/master',
      repo: '<your_git_repo_url>',
      path: '/home/<your_server_username>/appname',
      'post-setup': 'npm install',
      'post-deploy': 'pm2 startOrRestart ecosystem.config.js --env production',
    },
  },
};

Here’s a breakdown of the production object properties:

  • user: The username for authentication on the remote server.

  • host: An array of IP addresses of the remote servers.

  • ref: The git branch and remote to deploy, e.g., origin/master.

  • repo: The git repository's remote URL (HTTPS or SSH).

  • path: The directory on the remote server where the repository will be cloned.

  • post-setup: Commands or scripts to run post cloning.

  • post-deploy: Commands or scripts to run after deployment.

Before deploying to the specified host servers, ensure each has PM2 installed and the necessary permissions to clone the Git repository (e.g., the correct SSH key setup). Once set up, initiate the server provisioning with:

$ pm2 deploy production setup

If you encounter an error, it’s likely SSH-related, preventing PM2 from accessing the remote server or Git repository. Start your investigation by ensuring that the git clone <your_git_repo_url> command works on the remote server. For troubleshooting PM2 deployments, refer to this guide.

After successful provisioning, deploy the application:

$ pm2 deploy production
$ pm2 deploy production --force # if there are local changes

Conclusion

I wrote detailed guide to server your Node.js application with PM2. But there are more features than I explained in this blog. You can see more Here.

Thanks for your reading.

1
Subscribe to my newsletter

Read articles from Youth Dream directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Youth Dream
Youth Dream

Accomplished Software Engineer with 6 years of experience specializing in the Python, Node.js, React and Next.js ecosystem, and a proven track record in testing and performance optimization. Demonstrated leadership in guiding teams and implementing best practices in development.