Skip to content
Node.js nd libraries 5 min read

pm2: Production Process Manager

A bare node server.js is fine on your laptop, but in production it dies the moment the process crashes, your terminal closes, or the server reboots — and it can only ever use a single CPU core. pm2 is a production process manager that keeps Node.js apps alive forever, restarts them on failure, scales them across every core with zero-downtime reloads, and ships with built-in log management and monitoring. It is the de-facto standard for running long-lived Node services on a VM or bare-metal host.

Installing and starting an app

Install pm2 globally so its CLI is on your PATH:

npm install -g pm2

Starting an app is a single command. pm2 daemonizes the process, captures its stdout/stderr, and supervises it from then on.

pm2 start server.js --name api

Because pm2’s own runtime is CommonJS-based tooling, it happily runs both ES module and CommonJS apps — point it at your entry file regardless of "type": "module". The core CLI verbs are short and memorable:

pm2 list            # show all managed processes
pm2 logs api        # tail combined logs for one app
pm2 restart api     # hard restart (brief downtime)
pm2 reload api      # zero-downtime reload (cluster mode)
pm2 stop api        # stop but keep it in the list
pm2 delete api      # remove from pm2 entirely

Output:

┌────┬──────┬─────────┬─────────┬─────────┬──────────┬────────┐
│ id │ name │ mode    │ status  │ cpu     │ mem      │ uptime │
├────┼──────┼─────────┼─────────┼─────────┼──────────┼────────┤
│ 0  │ api  │ fork    │ online  │ 0%      │ 61.2mb   │ 12m    │
└────┴──────┴─────────┴─────────┴─────────┴──────────┴────────┘

Cluster mode and scaling across cores

Node.js is single-threaded for your JavaScript, so one process saturates exactly one core. Cluster mode launches N identical workers behind a shared socket and load-balances incoming connections across them — multiplying throughput without any code changes. Use -i max to spawn one worker per logical CPU, or -i 0 for the same effect.

pm2 start server.js --name api -i max

You can rescale a running app on the fly:

pm2 scale api 4     # set to exactly 4 workers

Cluster mode requires your app to be stateless between requests. Any in-memory session, cache, or counter lives in only one worker, so move shared state to Redis or a database. Also bind to a port (HTTP server) — pm2 shares the listening socket; it cannot load-balance arbitrary background scripts.

Auto-restart on crash

By default pm2 restarts any process that exits unexpectedly, with an exponential back-off to avoid a crash loop hammering your CPU. You can also restart on resource thresholds or a schedule.

OptionPurpose
--max-memory-restart 500MRestart a worker if it exceeds a memory ceiling (catches leaks)
--max-restarts 10Give up after N rapid restarts
--restart-delay 4000Wait between restarts (ms)
--cron-restart "0 3 * * *"Restart on a cron schedule (e.g. nightly)
--watchRestart when source files change (dev only)
pm2 start server.js --name api --max-memory-restart 500M --max-restarts 10

Log management

pm2 writes each app’s stdout and stderr to rotating files under ~/.pm2/logs/ and timestamps every line. pm2 logs streams them live; pm2 flush empties them.

pm2 logs api --lines 100
pm2 flush

For unbounded growth, install the rotation module so logs don’t fill the disk:

pm2 install pm2-logrotate
pm2 set pm2-logrotate:max_size 10M
pm2 set pm2-logrotate:retain 14

Even with pm2 rotating files, write structured JSON logs from your app (via Pino or Winston) so you can ship them to a central aggregator. pm2’s files are a safety net, not a logging strategy.

The ecosystem file

Repeating long CLI flags is error-prone. An ecosystem file captures the entire deployment — app names, instances, env vars, restart policy — as code you commit to your repo. Generate a starter and edit it:

pm2 init simple

Despite the .cjs extension, this works in ESM projects because pm2 loads the config with CommonJS:

// ecosystem.config.cjs
module.exports = {
  apps: [
    {
      name: "api",
      script: "./server.js",
      instances: "max",
      exec_mode: "cluster",
      max_memory_restart: "500M",
      env: {
        NODE_ENV: "development",
        PORT: 3000,
      },
      env_production: {
        NODE_ENV: "production",
        PORT: 8080,
      },
    },
  ],
};

Start everything described in the file, selecting the production env block:

pm2 start ecosystem.config.cjs --env production

Monitoring

pm2 monit opens a live terminal dashboard of CPU, memory, and log activity per process. For a one-off snapshot use pm2 status, and for deep details use pm2 describe.

pm2 monit
pm2 describe api

You can also bind metrics into your app with the @pm2/io package to expose custom counters and the pm2 plus web dashboard, but the built-in CLI covers most needs.

Startup scripts for production

The final piece is surviving a reboot. pm2 startup generates and installs the init script (systemd on most Linux distros) so the pm2 daemon launches at boot; pm2 save snapshots the current process list to be resurrected automatically.

pm2 startup            # prints a sudo command — run it once
pm2 save               # persist the current process list

Output:

[PM2] Init System found: systemd
[PM2] To setup the Startup Script, copy/paste the following command:
sudo env PATH=$PATH:/usr/bin pm2 startup systemd -u deploy --hp /home/deploy

After running the printed command and pm2 save, your apps come back online after any reboot with no manual intervention.

Best practices

  • Define deployments in a committed ecosystem file rather than ad-hoc CLI flags, and use env_production to separate config per environment.
  • Run cluster mode (-i max) only for stateless HTTP servers; keep shared state in Redis or a database.
  • Prefer pm2 reload over restart in production for zero-downtime deploys.
  • Set --max-memory-restart as a safety net against slow memory leaks.
  • Install pm2-logrotate so log files never fill the disk, and ship structured logs to a central system.
  • Always run pm2 startup + pm2 save so the fleet survives reboots.
  • Pin the pm2 version in your provisioning/Docker image to avoid surprise behavior changes across hosts.
Last updated June 14, 2026
Was this helpful?