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.
| Option | Purpose |
|---|---|
--max-memory-restart 500M | Restart a worker if it exceeds a memory ceiling (catches leaks) |
--max-restarts 10 | Give up after N rapid restarts |
--restart-delay 4000 | Wait between restarts (ms) |
--cron-restart "0 3 * * *" | Restart on a cron schedule (e.g. nightly) |
--watch | Restart 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_productionto separate config per environment. - Run cluster mode (
-i max) only for stateless HTTP servers; keep shared state in Redis or a database. - Prefer
pm2 reloadoverrestartin production for zero-downtime deploys. - Set
--max-memory-restartas a safety net against slow memory leaks. - Install
pm2-logrotateso log files never fill the disk, and ship structured logs to a central system. - Always run
pm2 startup+pm2 saveso the fleet survives reboots. - Pin the pm2 version in your provisioning/Docker image to avoid surprise behavior changes across hosts.