NodeJSProcess Management with PM2

Process Management with PM2

A plain node app.js dies when the process crashes, uses only one CPU core, and produces no daemon — you'd have to keep the terminal open. PM2 is the production process manager that fills all three gaps: it restarts crashed processes, runs one instance per CPU core (or any number you choose), daemonizes the app so it keeps running after logout, and survives server reboots. It's the go-to solution for running Node on a VM or bare metal, providing an ecosystem.config.js for declarative configuration, a handy CLI for runtime management, and built-in log aggregation. This page covers the core commands, cluster mode, the ecosystem file, log management, and startup on boot.

Running an app with PM2

Bash
npm install -g pm2

pm2 start app.js --name api         # start and daemonize
pm2 start dist/index.js --name api  # compiled TypeScript
pm2 list                            # status of all managed processes
pm2 logs api                        # tail logs (stdout + stderr, merged)
pm2 restart api                     # restart gracefully
pm2 stop api                        # stop without removing
pm2 delete api                      # stop and remove from PM2's list
┌────┬────────────────────┬──────────┬───────┬──────────┬──────┬──────────┐
│ id │ name               │ mode     │ ↺     │ status   │ cpu  │ memory   │
├────┼────────────────────┼──────────┼───────┼──────────┼──────┼──────────┤
│ 0  │ api                │ fork     │ 0     │ online   │ 0%   │ 52.0mb   │
└────┴────────────────────┴──────────┴───────┴──────────┴──────┴──────────┘
PM2 daemonizes your app and restarts it on crash — `pm2 list` shows the runtime status of every managed process
`pm2 start` launches your app as a **daemon** (runs in the background, survives terminal logout) and registers it with PM2's process list. On any crash — uncaught exception, OOM, segfault — PM2 **automatically restarts** it, so brief failures self-heal without human intervention. `pm2 list` gives a live dashboard of every process: name, mode, restart count, status, CPU, and memory. The restart count (`↺`) is a useful signal — a rising number means the app is crash-looping and needs attention. `pm2 logs` merges and tails stdout/stderr from all instances, while `pm2 logs --lines 200` shows recent history. This baseline already solves the "process dies and stays down" problem; cluster mode solves the "one core" problem.
Cluster mode — use all CPU cores

Bash
# -i max spawns one worker per logical CPU — fully uses the machine:
pm2 start dist/index.js --name api -i max

# Zero-downtime reload: new workers start BEFORE old ones stop:
pm2 reload api

# Scale up or down without stopping:
pm2 scale api 4     # run exactly 4 instances
Cluster mode requires stateless app instances — session/cache state must live in Redis, not in process memory
Cluster mode spawns **multiple Node processes** sharing the same port (via the built-in [cluster module](/nodejs/cluster-module) under the hood), so all CPU cores handle real traffic. But it *requires* that your app is **stateless**: each instance is independent, and a user's requests can land on any of them. Any state kept in process memory — cached values, session data, active WebSocket rooms — **is invisible to the other workers**. This is the same rule from [scaling strategies](/nodejs/scaling-strategies): put shared state in **[Redis](/nodejs/redis)** or your database, not in `Map`s or module-level variables. If your app *is* stateless already (pure request/response, state lives in the DB), cluster mode is a free horizontal-scaling win that requires zero code changes. Validate with `pm2 reload` — if requests fail during the reload, your app isn't truly stateless.
ecosystem.config.js — declarative config

ecosystem.config.js

JS
export default {
  apps: [
    {
      name: 'api',
      script: 'dist/index.js',
      instances: 'max',             // one per CPU core
      exec_mode: 'cluster',
      env_production: {
        NODE_ENV: 'production',
        PORT: 3000,
      },
      max_memory_restart: '512M',   // restart if a worker exceeds memory limit
      out_file: '/var/log/api/out.log',
      error_file: '/var/log/api/err.log',
      log_date_format: 'YYYY-MM-DD HH:mm:ss',
    },
  ],
}

Bash
pm2 start ecosystem.config.js --env production   # apply the env_production block
pm2 save                                           # persist current process list
Commit `ecosystem.config.js` — it's the declarative spec for how PM2 runs your app, replacing long command-line flags
Rather than memorizing command-line flags, **`ecosystem.config.js`** defines your app's PM2 config declaratively and **commits it** to the repo — so anyone deploying gets the same settings, and CI can use `pm2 start ecosystem.config.js`. It supports per-environment variable blocks (`env_production`), memory limits (PM2 restarts workers that exceed them, guarding against slow memory leaks), log file paths, instance count, and more. `pm2 save` serializes the current running process list to `~/.pm2/dump.pm2`; combined with startup hooks (below), it ensures the same processes restart after a reboot. Treat the ecosystem file as code: review it, version it, and use it as the single source of truth for your PM2 configuration.
Survive server reboots

Bash
# Generate and install a startup script for the current OS (systemd, upstart, etc.):
pm2 startup              # prints a command — run it with sudo as instructed
pm2 save                 # save the current list so it restores after reboot
pm2 unstartup            # remove the startup hook
`pm2 startup` + `pm2 save` ensures your processes restart automatically after a server reboot
By default PM2's process list is in memory — a server reboot wipes it. Two commands make it persistent: **`pm2 startup`** generates and installs a system-level startup hook (systemd unit, init script, etc.) that relaunches PM2 on boot, and **`pm2 save`** persists the current process list to a file that PM2 reads at startup. Together, a reboot → PM2 starts → restores all saved processes → your app is live again, automatically. Run `pm2 startup` once when provisioning a server and `pm2 save` after every change to the process list. In container/Kubernetes environments you typically don't use PM2 startup (the orchestrator manages restart), but on a plain VM or bare-metal server these two commands are essential for reliability across reboots.
Next
Put Nginx in front to handle TLS and routing: [Reverse Proxy with Nginx](/nodejs/reverse-proxy-nginx).