15%

Save 15% on All Hosting Services

Test your skills and get Discount on any hosting plan

Use code:

Skills
Get Started
09.10.2024

How to Install Node.js and PM2 on Ubuntu: A Complete Production Guide

Node.js is an asynchronous, event-driven JavaScript runtime built on Chrome's V8 engine, designed to execute JavaScript code server-side at high throughput. PM2 is a production-grade process manager for Node.js applications that provides daemonization, automatic crash recovery, log aggregation, cluster mode load balancing, and startup script generation β€” all from a single CLI interface.

This guide covers every installation method, configuration option, and operational pattern you need to run Node.js applications reliably on Ubuntu 20.04, 22.04, or 24.04 LTS in a production environment.

Prerequisites

Before proceeding, confirm the following:

  • Operating system: Ubuntu 20.04, 22.04, or 24.04 LTS (server or desktop)
  • User privileges: `sudo` or root access
  • Network access: Outbound HTTPS to download packages and scripts
  • curl installed: Run `sudo apt install curl -y` if not already present

If you are running this on a cloud server, a VPS Hosting environment is the most common deployment target for Node.js workloads, and everything in this guide applies directly.

Step 1: Update System Packages

Always synchronize your package index and apply pending upgrades before installing new software. Stale package metadata is a frequent source of dependency conflicts.

“`bash

sudo apt update

sudo apt upgrade -y

“`

After the upgrade completes, reboot if the kernel was updated:

“`bash

sudo reboot

“`

Step 2: Install Node.js β€” Choosing the Right Method

There are three primary installation methods for Node.js on Ubuntu. Each has distinct trade-offs in terms of version control, isolation, and system-wide availability.

Method Comparison

FeatureNodeSource (apt)NVMSystem apt (universe)
Version controlSingle pinned major versionMultiple versions per userTypically outdated LTS
System-wide installYesNo (per-user by default)Yes
Switching versionsRequires re-running setup script`nvm use <version>`Not supported
Best forCI/CD, single-version serversDevelopment, multi-projectQuick testing only
sudo required for npm globalsYesNoYes
Production suitabilityHighMediumLow

NodeSource maintains up-to-date Debian/Ubuntu repositories for every active Node.js release line. This is the preferred approach for production servers where a single, stable version is required system-wide.

Add the NodeSource repository for the current LTS release:

“`bash

curl -fsSL https://deb.nodesource.com/setup_lts.x | sudo -E bash –

“`

This script performs several actions: it detects your Ubuntu version, adds the appropriate NodeSource apt repository, imports the GPG signing key, and runs `apt-get update`. The `-E` flag preserves your environment variables when escalating to sudo, which matters if you have proxy settings configured.

Install Node.js and npm:

“`bash

sudo apt install nodejs -y

“`

The NodeSource package bundles both `node` and `npm` in a single `nodejs` package. Unlike the Ubuntu universe package, it does not split them.

Verify the installation:

“`bash

node -v

npm -v

“`

Expected output example:

“`

v20.14.0

10.7.0

“`

Pinning to a specific major version: If you need Node.js 18 instead of the current LTS, replace `setup_lts.x` with `setup_18.x` in the curl command. Available streams include `setup_18.x`, `setup_20.x`, and `setup_22.x`.

NVM (Node Version Manager) installs Node.js into your home directory, requiring no root privileges for either Node.js itself or globally installed npm packages. This eliminates the permission issues that commonly arise when running `npm install -g` on system-installed Node.js.

Install NVM:

“`bash

curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash

“`

Check the official NVM repository for the latest release tag before running this command β€” the version number in the URL changes with each release.

Reload your shell environment:

“`bash

source ~/.bashrc

“`

If you use Zsh, source `~/.zshrc` instead. The NVM installer appends its initialization block to whichever shell configuration file it detects.

Install the latest LTS version of Node.js:

“`bash

nvm install –lts

“`

Install a specific version alongside the LTS:

“`bash

nvm install 18

nvm install 20

“`

Switch between installed versions:

“`bash

nvm use 20

nvm alias default 20

“`

The `alias default` command sets the version that activates in new shell sessions, which is critical for scripts and cron jobs that do not source your interactive shell profile.

Verify:

“`bash

node -v

npm -v

“`

Critical NVM pitfall for production: Because NVM is user-scoped, PM2's startup scripts and systemd units will not have access to the NVM-managed Node.js binary unless you explicitly configure the path. See Step 5 for how to handle this correctly.

Step 3: Install PM2

PM2 is distributed as an npm package and should be installed globally so its CLI is available system-wide.

Install PM2:

“`bash

sudo npm install -g pm2

“`

If you installed Node.js via NVM, omit `sudo`:

“`bash

npm install -g pm2

“`

Verify the installation:

“`bash

pm2 -v

“`

Install a specific PM2 version (useful when pinning infrastructure):

“`bash

sudo npm install -g pm2@5.3.1

“`

PM2 version 5.x is the current stable line. It introduced significant improvements to the web dashboard (pm2 plus), log rotation, and module system compared to v4.

Step 4: Managing Node.js Applications with PM2

Starting an Application

Navigate to your application directory and start it:

“`bash

cd /var/www/my-app

pm2 start app.js –name "my-app"

“`

Always assign a `–name` flag. Without it, PM2 uses the filename as the process name, which becomes ambiguous when you have multiple services.

Starting with additional options:

“`bash

pm2 start app.js –name "my-app" –watch –max-memory-restart 300M

“`

  • `–watch`: Restarts the process when source files change (useful in staging, not recommended in production)
  • `–max-memory-restart 300M`: Automatically restarts the process if it exceeds 300 MB of RSS memory β€” a critical safeguard against memory leaks

Viewing Running Processes

“`bash

pm2 list

“`

This outputs a table showing process ID, name, mode (fork/cluster), PID, status, CPU usage, memory consumption, and restart count.

For a real-time dashboard:

“`bash

pm2 monit

“`

Controlling Processes

“`bash

pm2 restart my-app # Graceful restart

pm2 reload my-app # Zero-downtime reload (cluster mode only)

pm2 stop my-app # Stop without removing from process list

pm2 delete my-app # Stop and remove from process list

“`

The difference between `restart` and `reload`: `restart` kills the process and starts a new one, causing a brief downtime. `reload` (available only in cluster mode) cycles workers one at a time, maintaining availability throughout. Always use `reload` in production cluster deployments.

Log Management

“`bash

pm2 logs # Stream logs from all processes

pm2 logs my-app # Stream logs for a specific process

pm2 logs my-app –lines 200 # Show last 200 lines

pm2 flush # Clear all log files

“`

PM2 stores logs at `~/.pm2/logs/` by default. For production servers, install the log rotation module to prevent disk exhaustion:

“`bash

pm2 install pm2-logrotate

pm2 set pm2-logrotate:max_size 50M

pm2 set pm2-logrotate:retain 10

pm2 set pm2-logrotate:compress true

“`

This rotates logs when they reach 50 MB, retains 10 compressed archives, and prevents runaway log growth β€” a common issue on long-running servers that is frequently overlooked until disk space runs out.

Step 5: Configure PM2 to Start on System Boot

PM2 must be configured to survive server reboots. This is handled through a systemd service unit generated by PM2 itself.

Generate the startup command:

“`bash

pm2 startup

“`

PM2 will output a command tailored to your init system and current user. It looks like this:

“`

[PM2] Init System found: systemd

[PM2] To setup the Startup Script, copy/paste the following command:

sudo env PATH=$PATH:/usr/bin /usr/lib/node_modules/pm2/bin/pm2 startup systemd -u ubuntu –hp /home/ubuntu

“`

Copy and execute that exact command. Do not modify it β€” the path to the PM2 binary and the home directory must match your system's configuration.

NVM-specific startup configuration: If you installed Node.js via NVM, the path in the generated command will point to your NVM-managed binary. This is correct, but you must ensure the NVM version is set as default before running `pm2 startup`:

“`bash

nvm alias default 20

pm2 startup

“`

Save the current process list:

“`bash

pm2 save

“`

This writes the process list to `~/.pm2/dump.pm2`. On reboot, the systemd unit reads this file and restores all saved processes. If you add or remove applications later, run `pm2 save` again to update the snapshot.

Verify the systemd unit is active:

“`bash

systemctl status pm2-ubuntu

“`

Replace `ubuntu` with your actual username.

Step 6: Production Deployment with PM2 Ecosystem Files

For anything beyond a single-script application, use PM2's ecosystem configuration file. It provides reproducible, version-controlled process definitions and eliminates the need to remember long CLI flags.

Generating the Ecosystem File

“`bash

pm2 ecosystem

“`

This creates `ecosystem.config.js` in the current directory.

A Production-Ready Ecosystem Configuration

“`javascript

module.exports = {

apps: [

{

name: 'api-server',

script: './src/server.js',

instances: 'max',

exec_mode: 'cluster',

watch: false,

max_memory_restart: '500M',

log_date_format: 'YYYY-MM-DD HH:mm:ss Z',

error_file: '/var/log/pm2/api-server-error.log',

out_file: '/var/log/pm2/api-server-out.log',

merge_logs: true,

env: {

NODE_ENV: 'development',

PORT: 3000

},

env_production: {

NODE_ENV: 'production',

PORT: 8080

}

},

{

name: 'worker',

script: './src/worker.js',

instances: 2,

exec_mode: 'fork',

cron_restart: '0 2 * * *',

env_production: {

NODE_ENV: 'production'

}

}

]

};

“`

Key configuration decisions explained:

  • `instances: 'max'`: PM2 automatically spawns one worker per logical CPU core. On a 4-core server, this creates 4 Node.js processes, fully utilizing the hardware.
  • `exec_mode: 'cluster'`: Uses Node.js's built-in cluster module. All instances share the same port through the operating system's socket load balancing.
  • `exec_mode: 'fork'`: Runs the process as a standalone child process. Required for applications that are not HTTP servers (queue workers, scheduled jobs, WebSocket servers with sticky sessions).
  • `merge_logs: true`: Combines stdout from all cluster instances into a single log file, making log analysis significantly easier.
  • `cron_restart`: Schedules automatic restarts using cron syntax. Useful for workers that accumulate state or for applying nightly configuration changes.

Starting with the Ecosystem File

“`bash

pm2 start ecosystem.config.js –env production

pm2 save

“`

Zero-Downtime Deployment Workflow

When deploying a new version of your application:

“`bash

git pull origin main

npm install –production

pm2 reload ecosystem.config.js –env production

“`

`pm2 reload` sends a `SIGINT` to each worker one at a time, waits for the new worker to become ready, then terminates the old one. Your application must handle `SIGINT` gracefully and signal readiness using `process.send('ready')` for this to work correctly.

Graceful shutdown handler in your application:

“`javascript

process.on('SIGINT', () => {

server.close(() => {

console.log('HTTP server closed');

process.exit(0);

});

});

“`

Step 7: PM2 Monitoring and Observability

Built-in Monitoring

“`bash

pm2 monit

“`

Displays real-time CPU and memory graphs for each process in the terminal.

Process Information

“`bash

pm2 show my-app

“`

Outputs detailed metadata: uptime, restart count, versioning, interpreter path, environment variables, and log file locations.

PM2 Web Dashboard (PM2 Plus)

PM2 offers a hosted monitoring dashboard at pm2.io. Connect your server:

“`bash

pm2 link <secret_key> <public_key>

“`

This provides historical metrics, alerting, and remote process management β€” particularly valuable when managing multiple servers or when you need visibility without SSH access.

Step 8: Updating Node.js and PM2

Updating PM2

“`bash

sudo npm install -g pm2@latest

pm2 update

“`

`pm2 update` is essential after upgrading the PM2 binary β€” it updates the in-memory PM2 daemon without interrupting running processes.

Updating Node.js via NodeSource

Re-run the setup script for the new major version:

“`bash

curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash –

sudo apt install nodejs -y

“`

After updating Node.js, restart PM2 to ensure it uses the new binary:

“`bash

pm2 restart all

“`

Updating Node.js via NVM

“`bash

nvm install 22

nvm alias default 22

nvm use 22

pm2 restart all

“`

If you changed the default NVM version, regenerate the PM2 startup script to update the binary path in the systemd unit:

“`bash

pm2 unstartup

pm2 startup

pm2 save

“`

Security Hardening Considerations

Running Node.js applications in production requires attention beyond process management:

  • Run as a non-root user: Never run PM2 or Node.js as root. Create a dedicated system user (`adduser –system –group nodeapp`) and run PM2 under that account.
  • Environment variable management: Do not hardcode secrets in `ecosystem.config.js`. Use a `.env` file loaded via `dotenv`, or inject secrets through your deployment pipeline. The ecosystem file is typically committed to version control.
  • Reverse proxy: Place Nginx or Caddy in front of your Node.js application. This handles TLS termination, static file serving, rate limiting, and request buffering. Pair this with an SSL Certificates solution to enforce HTTPS.
  • Firewall rules: Block direct access to your Node.js port (e.g., 3000, 8080) from the public internet. Only the reverse proxy should communicate with Node.js.
  • Resource limits: Set `max_memory_restart` in PM2 and configure system-level `ulimit` values to prevent a single runaway process from destabilizing the server.

For high-traffic production deployments where resource isolation is critical, a Dedicated Servers environment provides full hardware control and eliminates the noisy-neighbor problem inherent to shared infrastructure.

Choosing the Right Hosting Environment for Node.js

WorkloadRecommended EnvironmentRationale
Personal projects, staging[VPS Hosting](https://alexhost.com/vps/)Cost-effective, full root access, scalable
High-traffic APIs[Dedicated Servers](https://alexhost.com/dedicated-servers/)Predictable performance, no resource contention
ML inference + Node.js[GPU Hosting](https://alexhost.com/gpu-hosting/)Offload compute-intensive tasks to GPU workers
Managed control panel[VPS with cPanel](https://alexhost.com/vps/control-panels/cpanel-vps/)GUI-based process and file management

Technical Decision Checklist

Use this checklist before deploying Node.js and PM2 to production:

  • [ ] Node.js installed via NodeSource (server) or NVM (development) β€” not the Ubuntu universe package
  • [ ] PM2 installed globally with the correct permissions for your installation method
  • [ ] Application started with `–name` flag and a defined `–max-memory-restart` threshold
  • [ ] Cluster mode enabled for HTTP servers; fork mode used for background workers
  • [ ] `pm2 startup` executed and the generated command run with sudo
  • [ ] `pm2 save` run after all processes are configured
  • [ ] Log rotation module installed and configured
  • [ ] Nginx or Caddy configured as a reverse proxy with TLS
  • [ ] Application handles `SIGINT` gracefully for zero-downtime reloads
  • [ ] Secrets managed outside of `ecosystem.config.js`
  • [ ] PM2 systemd unit verified with `systemctl status pm2-<username>`
  • [ ] Firewall blocks direct access to Node.js ports from the public internet

FAQ

What is the difference between PM2 fork mode and cluster mode?

Fork mode spawns the application as a single child process β€” one instance, one CPU core utilized. Cluster mode uses Node.js's native cluster module to spawn multiple worker processes that all share the same TCP port, enabling true multi-core utilization and zero-downtime reloads. Use cluster mode for HTTP/HTTPS servers and fork mode for workers, cron jobs, or applications that maintain internal state incompatible with multi-process sharing.

Why does PM2 not restart after a server reboot even though I ran `pm2 startup`?

The most common cause is that `pm2 save` was not run after configuring processes, so the dump file is empty or missing. The second most common cause is an NVM path mismatch: if the NVM default version was changed after generating the startup script, the systemd unit points to a non-existent binary. Resolve this by running `pm2 unstartup`, setting the correct NVM default, then re-running `pm2 startup` and `pm2 save`.

Can PM2 manage non-Node.js processes?

Yes. PM2 can manage any executable by specifying the interpreter. For example: `pm2 start script.py –interpreter python3`. This makes PM2 useful as a general-purpose process supervisor for mixed-language microservice architectures.

How do I run multiple Node.js applications on different ports behind a single server?

Define each application as a separate entry in `ecosystem.config.js` with distinct `PORT` environment variables. Configure Nginx as a reverse proxy with separate `server` blocks or `location` directives routing to each port. All applications share the same PM2 daemon and are managed through a single `pm2 list` view.

Should I use NVM or NodeSource for a production VPS?

NodeSource is generally preferable for production servers. It installs Node.js as a system package, making it available to all users and system services without shell initialization dependencies. NVM's per-user, per-shell activation model introduces subtle failure modes in systemd units, cron jobs, and CI/CD pipelines that run outside an interactive shell session. Reserve NVM for local development machines where managing multiple Node.js versions simultaneously is a genuine requirement.

15%

Save 15% on All Hosting Services

Test your skills and get Discount on any hosting plan

Use code:

Skills
Get Started