Jesse A. Green / notebook

PM2 Administrator & Architecture Guide

Generated June 2026 from Unitech/pm2 at ba62cae

AI-generated documentation. The library is checked by sampling (three repositories so far); this guide was not individually reviewed. Verify against the project before relying on it.

This guide is the deep reference for operators, architects, and developers who run, deploy, secure, extend, or contribute to PM2. It covers PM2's internal architecture, its complete configuration surface, deployment patterns, operational procedures, security posture, and source-level internals. It is written against PM2 7.0.1 (commit ba62cae) and written from the source.

For day-to-day command usage, see the companion PM2 User Guide. This document assumes you already know how to start and manage applications and focuses on how PM2 works and how to operate it at scale.


1. Executive Summary

PM2 is a production process manager for Node.js and Bun applications, distributed as the pm2 npm package (≈43k GitHub stars, AGPL-3.0). It daemonizes applications, supervises them (restart on crash, on memory ceiling, on schedule), load-balances them across CPU cores using Node's cluster module, reloads them with zero downtime, aggregates their logs, and restores them after a reboot.

Architecturally, PM2 is a client/daemon system. The pm2 command you type is a short-lived client that connects over a Unix domain socket (a named pipe on Windows) to a long-lived daemon — referred to in the source as "God" — which actually owns and supervises your application processes. The same client code is exposed as a programmatic Node.js API (require('pm2')).

Attribute Value
Package pm2
Version documented 7.0.1
Language JavaScript (CommonJS)
Runtime Node.js ≥ 18.0.0, or Bun ≥ 1.1.25
License AGPL-3.0
Platforms Linux, macOS, Windows
State directory ~/.pm2 (override via PM2_HOME)
Transport axon / axon-rpc over Unix domain sockets
Process model cluster.fork (cluster mode) or child_process.spawn (fork mode)

Important

The single most important operational fact about PM2 is the client/daemon split. Upgrading the pm2 npm package does not upgrade the running daemon — you must run pm2 update to reload the in-memory daemon with the new code. Most "PM2 acting weird after upgrade" incidents trace back to this.


2. Architecture

2.1 High-level component diagram

                       ┌─────────────────────────────────────────────┐
   You / CI / Node     │                Your machine                 │
        │              │                                             │
        ▼              │   ┌──────────────┐   require('pm2')         │
  pm2 / pm2-runtime ───┼──▶│  CLI client  │◀────────── Node program  │
  pm2-dev / pm2-docker │   │ (lib/API.js) │                          │
        │              │   └──────┬───────┘                          │
        │              │          │ axon-rpc over rpc.sock           │
        │              │          ▼                                  │
        │              │   ┌──────────────────────────────────────┐ │
        │              │   │      PM2 Daemon  ("God")             │ │
        │              │   │      lib/Daemon.js + lib/God/*       │ │
        │              │   │                                      │ │
        │              │   │   clusters_db { pm_id -> proc }      │ │
        │              │   │   ┌────────────┐   ┌──────────────┐  │ │
        │              │   │   │ ClusterMode│   │  ForkMode    │  │ │
        │              │   │   │cluster.fork│   │child spawn   │  │ │
        │              │   │   └─────┬──────┘   └──────┬───────┘  │ │
        │              │   └─────────┼─────────────────┼──────────┘ │
        │              │             ▼                 ▼            │
        │              │      ┌───────────┐     ┌───────────┐       │
        │              │      │ app inst.0│ ... │ app (fork)│       │
        │              │      └───────────┘     └───────────┘       │
        │              │             │ log:out / log:err / msg      │
        │  pub.sock    │             ▼                              │
        │ (event bus)  │      ~/.pm2/logs/*.log                     │
        │              │                                            │
        │              │   ┌──────────────────────────────────────┐ │
        │              │   │  Interactor daemon (optional, PM2+)  │ │
        │              │   │  modules/pm2-io-agent (separate proc)│ │
        │              │   └──────────────────────────────────────┘ │
        └──────────────┴────────────────────────────────────────────┘

2.2 The request lifecycle

A command such as pm2 restart api flows like this:

  1. CLI parse — lib/binaries/CLI.js (built on commander) parses global options and the restart sub-command, then instantiates the API class (lib/API.js) and calls pm2.connect().
  2. Connect — lib/Client.js checks whether the daemon is alive (by pinging rpc.sock in ~/.pm2). If not, it forks and detaches a new daemon process, then opens an axon-rpc connection.
  3. Client-side orchestration — API.restart() resolves the target (api) to one or more pm_ids by querying the daemon, then calls Client.executeRemote('restartProcessId', {id, env}, cb) for each.
  4. Daemon action — In the daemon, God.restartProcessId (lib/God/ActionMethods.js) looks up the process in clusters_db, stops it (SIGINT → SIGKILL after kill_timeout), and re-executes it via God.executeApp, which dispatches to ClusterMode or ForkMode.
  5. Event broadcast — The daemon emits process events (process:event, log:out, log:err) on its internal bus, published over pub.sock so subscribers (e.g. pm2 logs, the PM2+ agent) receive them.
  6. Disconnect — The client closes its RPC connection and exits. The daemon keeps running.

2.3 Key source modules

Module Role
index.js Programmatic entry point: exports new API and API as .custom
lib/API.js The API/client class — start, stop, restart, reload, delete, list, etc. ≈2,600 lines
lib/Client.js Connects to (and launches) the daemon; RPC transport
lib/Daemon.js The daemon process bootstrap
lib/God.js + lib/God/* The in-daemon process manager (the "God")
lib/God/ActionMethods.js RPC handlers: start/stop/restart/delete/signal/monitor/report
lib/God/ClusterMode.js Spawns instances via Node's cluster.fork
lib/God/ForkMode.js Spawns processes via child_process.spawn with an IPC channel
lib/God/Reload.js softReload / hardReload zero-downtime logic
lib/God/Methods.js Process bookkeeping, killProcess, tree-kill, dead-detection
lib/Common.js Config parsing, env merging, attribute resolution, validation
lib/Configuration.js Namespaced key/value config (pm2 set/get)
lib/ProcessContainer*.js The wrapper that hosts your script inside each process
lib/API/Startup.js Init-system startup script generation
lib/API/Serve.js The pm2 serve static file server
lib/API/Modules/* Module install/update/uninstall (NPM + TAR)
lib/binaries/* The four CLI front-ends (CLI, DevCLI, Runtime, Runtime4Docker)
modules/pm2-axon, pm2-axon-rpc The socket messaging / RPC transport
modules/pm2-io-agent The PM2+ interactor daemon (separate process)
modules/pm2-io-bpm The @pm2/io probes (metrics, actions, tracing)
constants.js, paths.js Tunable constants and the ~/.pm2 file layout

2.4 Process model: fork vs. cluster

PM2 runs every application in one of two modes, decided by exec_mode (set implicitly by -i/instances):

Fork mode (lib/God/ForkMode.js) — the default. PM2 uses child_process.spawn with stdio: ['pipe','pipe','pipe','ipc']. This works for any program: Node, Bun, Python, Ruby, bash, or a raw binary (interpreter: 'none'). Node scripts are launched through a wrapper (ProcessContainerFork.js; ProcessContainerForkBun.js for Bun). stdout/stderr are piped to the daemon, which writes them to log files and emits log:out/log:err events. Fork mode supports stdin interaction (pm2 send / pm2 attach).

Cluster mode (lib/God/ClusterMode.js) — Node.js only. PM2 uses Node's cluster.fork, passing the process environment as a stringified pm2_env. The OS-level load balancer distributes incoming connections across instances on the same port. Cluster mode is what enables -i max multi-core scaling and true zero-downtime reload.

Note

Cluster mode passes the environment as a JSON string because Node's cluster IPC flattens nested objects ({args:["a","b"]} would degrade to "a, b"). The child unpacks process.env.pm2_env back into a structured object. This is why each instance receives a pm2_env variable.

2.5 Zero-downtime reload internals

reload is implemented in lib/God/Reload.js and only performs a rolling restart when the target is online and in cluster mode; otherwise it falls back to restartProcessId.

  • hardReload (the default reload): the old worker is renamed to a temporary _old_<id> key, a new worker is spawned, and PM2 waits for the new worker's listening event (or a ready IPC message if wait_ready is set). On success — or after listen_timeout / GRACEFUL_LISTEN_TIMEOUT (3000ms) — the old worker is deleted.
  • softReload (startOrGracefulReload): similar, but the old worker is first asked to shut down via a shutdown IPC message and given GRACEFUL_TIMEOUT (8000ms) to disconnect cleanly before deletion.

A reload lock (~/.pm2/reload.lock, timeout RELOAD_LOCK_TIMEOUT = 30000ms) prevents two reloads from running at once; --force overrides it.

2.6 Process termination

God.killProcess (lib/God/Methods.js) implements a staged shutdown:

  1. If KILL_USE_MESSAGE or shutdown_with_message is set, send a shutdown IPC message first.
  2. Otherwise send KILL_SIGNAL (default SIGINT). With treekill enabled (the default), the entire process tree is signalled; with --no-treekill, only the main process.
  3. God.processIsDead polls the process tree. If anything is still alive after kill_timeout (default 1600ms), PM2 escalates to SIGKILL.

3. The ~/.pm2 State Directory

PM2 keeps all of its persistent state on the local filesystem in ~/.pm2 (resolved in paths.js). There is no database, no Redis, no external broker. Override the location with PM2_HOME.

Path Contents
~/.pm2/pm2.log The daemon's own log
~/.pm2/pm2.pid The daemon's pid file
~/.pm2/logs/ Per-app stdout/stderr log files
~/.pm2/pids/ Per-app pid files
~/.pm2/modules/ Installed PM2 modules
~/.pm2/dump.pm2 The saved process list (from pm2 save)
~/.pm2/dump.pm2.bak Backup of the previous dump
~/.pm2/rpc.sock Daemon RPC control socket
~/.pm2/pub.sock Daemon event-bus publish socket
~/.pm2/reload.lock Reload mutual-exclusion lock
~/.pm2/conf.js PM2 configuration
~/.pm2/module_conf.json Module configuration store
~/.pm2/interactor.sock PM2+ interactor RPC socket
~/.pm2/agent.log, agent.pid, agent.json5 PM2+ interactor state

On Windows, the three sockets become named pipes (\\.\pipe\rpc.sock, etc.).

Tip

Running multiple isolated PM2 instances on one host is simply a matter of giving each a distinct PM2_HOME. Each gets its own daemon, sockets, logs, and dump file. This is the standard way to separate per-user or per-service PM2 deployments without containers.

Individual paths can be overridden with PM2_-prefixed environment variables (for example PM2_PID_FILE_PATH), with the exception of PM2_HOME/PM2_ROOT_PATH themselves.


4. Configuration Mastery

PM2 has three distinct configuration surfaces. Do not confuse them.

4.1 Application configuration (the ecosystem file)

This is the primary configuration artifact: a JavaScript, JSON, or YAML file (default name ecosystem.config.js) describing your apps. Generate one with pm2 ecosystem. Every field is documented in the User Guide's full field reference; the authoritative source is the StartOptions interface in types/index.d.ts. The most operationally significant fields:

Field Default Operational note
exec_mode fork Set cluster for zero-downtime reload and multi-core scaling
instances 1 max/0 = one per core; -1 = cores minus one
max_memory_restart — The key guardrail against memory leaks in production
kill_timeout 1600 Raise for apps that need longer to drain connections
listen_timeout — Raise for apps slow to bind their port on reload
wait_ready false Require an explicit process.send('ready') before cutover
min_uptime / max_restarts — Together, they stop a broken deploy from looping forever
cron_restart — Scheduled restarts (cron syntax)
autorestart true Set false for one-shot jobs
namespace default Group apps for bulk operations
env / env_<name> — Base + named environment blocks, selected with --env

Environment blocks are merged by Common.mergeEnvironmentVariables: --env production overlays the env_production block on top of env.

Warning

By default restart/reload do not refresh the environment — a deliberate safety feature. CI pipelines that change env vars must pass --update-env or the new values are silently ignored.

4.2 PM2 runtime tunables (environment variables)

These change the behavior of PM2 itself and are read in constants.js at daemon start. Set them in the environment of the user/service that launches the daemon.

Variable Default Effect
PM2_HOME ~/.pm2 State directory
PM2_CONCURRENT_ACTIONS 2 Parallel start/restart/reload fan-out
PM2_KILL_TIMEOUT 1600 ms before SIGINT escalates to SIGKILL
PM2_KILL_SIGNAL SIGINT First stop signal
PM2_KILL_USE_MESSAGE false Stop via IPC shutdown message instead of signal
PM2_GRACEFUL_TIMEOUT 8000 ms for graceful disconnect on soft reload
PM2_GRACEFUL_LISTEN_TIMEOUT 3000 ms to wait for listening/ready on reload
PM2_RELOAD_LOCK_TIMEOUT 30000 Reload lock lifetime
EXP_BACKOFF_RESET_TIMER 30000 Uptime after which exp-backoff delay resets
PM2_WORKER_INTERVAL 30000 Internal housekeeping worker interval
PM2_API_PORT 9615 Port for the HTTP process API (--web)
PM2_API_IPADDR 0.0.0.0 Bind address for the HTTP process API
PM2_WEB_STRIP_ENV_VARS false Strip env vars from HTTP API output
PM2_LOG_DATE_FORMAT YYYY-MM-DDTHH:mm:ss Default log timestamp format
PM2_DEBUG false Verbose internal debugging
PM2_DISCRETE_MODE — Suppress the startup banner
PM2_SILENT — Hide all PM2 output
PM2_OPTIMIZE_MEMORY false Low-memory operating mode
PM2_NODE_OPTIONS — (Deprecated) Node options injected into forked apps

4.3 Namespaced key/value config (pm2 set / get)

The third surface is a small persistent key/value store (lib/Configuration.js, backed by ~/.pm2/module_conf.json), used mostly to configure modules:

pm2 set pm2:sysmonit true                 # PM2 built-in setting
pm2 set pm2-logrotate:max_size 10M        # module setting (namespace:key)
pm2 get pm2-logrotate:max_size
pm2 unset pm2-logrotate:max_size
pm2 conf                                   # view all

Note

After changing a pm2: setting that affects the daemon (such as pm2:sysmonit), run pm2 update so the daemon re-reads it.


5. Deployment Strategies

5.1 Bare-metal / VM with systemd

The canonical production setup on Linux:

# 1. Install
npm install pm2 -g

# 2. Generate and enable the boot hook (run the sudo command it prints)
pm2 startup systemd -u deploy --hp /home/deploy

# 3. Start apps from an ecosystem file
pm2 start ecosystem.config.js --env production

# 4. Freeze the list so it restores on boot
pm2 save

Deployment checklist:

  • [ ] PM2 installed globally for the deploy user
  • [ ] pm2 startup run and the printed sudo command executed
  • [ ] Apps started in cluster mode for zero-downtime reloads
  • [ ] max_memory_restart set as a leak guardrail
  • [ ] pm2 save run after the final app list is correct
  • [ ] pm2-logrotate installed and sized
  • [ ] A health check confirms the app responds, not just that PM2 reports online

Init systems supported by pm2 startup: systemd, upstart, systemv, openrc, launchd, rcd, rcd-openbsd, smf.

5.2 Containers (Docker / Kubernetes)

Inside a container, prefer pm2-runtime (a.k.a. pm2-docker) — it runs in the foreground, streams logs to stdout/stderr, and exits when the app does, matching container lifecycle expectations.

FROM node:20
WORKDIR /app
COPY . .
RUN npm install pm2 -g && npm install --production
CMD ["pm2-runtime", "ecosystem.config.js"]

Operationally relevant pm2-runtime flags: -i <n> (instances), --no-autorestart, --max-memory-restart, --web [port] (expose the HTTP API), --delay <s>, --no-auto-exit, --only <app>, and log-format flags (--json, --format, --raw, --formatted).

Caution

Do not run pm2 (the daemonizing CLI) as the container entrypoint. A backgrounded daemon means the container's main process exits immediately and the orchestrator considers the container dead. Always use pm2-runtime in containers.

5.3 Zero-downtime deploy flow

git pull
npm ci --production
pm2 reload ecosystem.config.js --env production --update-env

pm2 reload (cluster mode) rolls instances one at a time so traffic is never dropped. Pair it with wait_ready/listen_timeout for apps that need warm-up.

5.4 Git-based versioning

For apps that are git checkouts, PM2 can move them along history in place: pm2 pull <app> [commit], pm2 forward <app>, pm2 backward <app>. The declarative pm2 deploy system (backed by pm2-deploy) reads a deploy block from the ecosystem file for SSH-based multi-host deploys.


6. Operations

6.1 Daily operational commands

Task Command
Inspect process table pm2 ls
Machine-readable list pm2 jlist (pipe to jq)
Detail one app pm2 describe <app>
Live dashboard pm2 monit
Host vitals pm2 slist --tree
Stream logs pm2 logs [app]
Diagnostic report pm2 report
Probe daemon pm2 ping

6.2 Backup and restore

The "backup" of a PM2 deployment is the dump file:

pm2 save                                  # writes ~/.pm2/dump.pm2 (+ .bak)
cp ~/.pm2/dump.pm2 /backups/pm2-$(date +%F).pm2

Restore on a fresh host by placing the dump and running:

pm2 resurrect

Note

God.dumpProcessList deliberately refuses to overwrite a non-empty dump with an empty process list (guards against clobbering your saved apps if the daemon crashed). It also keeps a .bak. Force an empty/explicit dump with pm2 dump --force.

6.3 Upgrading PM2

npm install pm2@latest -g
pm2 update                                # reload the in-memory daemon

pm2 update saves the process list, kills and relaunches the daemon on the new code, restarts the RPC layer, resurrects the apps, and re-launches modules and (if linked) the PM2+ agent. PM2 warns on every command when the daemon is out of date.

6.4 Log rotation

PM2's native logs grow unbounded. Standardize on pm2-logrotate:

pm2 install pm2-logrotate
pm2 set pm2-logrotate:max_size 10M
pm2 set pm2-logrotate:retain 30
pm2 set pm2-logrotate:compress true

After external rotation, run pm2 reloadLogs so PM2 reopens its file handles.

6.5 Health monitoring

pm2 ls reporting online only means the process exists — it does not mean the app is serving correctly. Layer an application-level health check (HTTP probe, queue depth, etc.) on top. max_memory_restart, min_uptime, and max_restarts form the built-in self-healing guardrails; the HTTP process API (--web, port 9615) exposes process metrics for external scraping.


7. Security

Important

PM2 is local infrastructure: a daemon plus Unix sockets, with no built-in authentication or network listener by default. Its security posture is dominated by filesystem permissions and what you ask it to run.

7.1 Trust boundaries

  • Control sockets — rpc.sock and pub.sock live in ~/.pm2 and are protected by filesystem permissions. Anyone who can read/write them can fully control your processes. Keep ~/.pm2 owned by, and readable only by, the PM2 user (PM2_SOCKET_USER / PM2_SOCKET_GROUP can adjust socket ownership).
  • The HTTP process API — pm2 web / --web opens an HTTP server on PM2_API_PORT (9615), bound by default to 0.0.0.0 (PM2_API_IPADDR). It exposes process metadata including environment variables. Do not expose this port publicly. Bind it to 127.0.0.1, or set PM2_WEB_STRIP_ENV_VARS=true to redact env vars, or leave it disabled (the default).
  • The static file server — pm2 serve can require HTTP Basic Auth (--basic-auth-username / --basic-auth-password). Treat the credentials as secrets and prefer environment injection over the command line.

7.2 Privilege management

  • PM2 can drop privileges per app via uid/gid (passed to spawn). Run apps as the least-privileged user possible.
  • pm2 startup typically needs sudo to write an init-system unit; the resulting service runs as the user named by -u. Pin that user explicitly rather than running everything as root.
  • The daemon runs as whichever user first invoked PM2. A root-owned daemon supervising user apps is a common but risky default — prefer a dedicated deploy/service user.

7.3 Secrets handling

PM2+ keys (PM2_SECRET_KEY / KEYMETRICS_SECRET, PM2_PUBLIC_KEY / KEYMETRICS_PUBLIC) and application secrets flow through the process environment. They appear in pm2 env <id>, in pm2 describe, and — unless stripped — in the HTTP API. Source secrets from a secrets manager or root-only env files, not from the command line (which lands in shell history and ps output).

7.4 Dependency and supply-chain hygiene

PM2 vendors several modules (pm2-axon, pm2-io-agent, pm2-io-bpm) and depends on networked libraries such as ws. The project's own issue tracker shows routine security bumps (e.g. ws CVE updates). Keep PM2 current with npm install pm2@latest -g && pm2 update, and audit transitive dependencies as part of your pipeline.

7.5 Network exposure summary

Surface Default state Hardening
rpc.sock / pub.sock Local socket, fs-permission gated Restrict ~/.pm2 ownership/mode
HTTP process API (--web, 9615) Off unless requested; binds 0.0.0.0 Keep off, or bind 127.0.0.1 + strip env vars
pm2 serve Off unless requested Enable Basic Auth; front with TLS
PM2+ interactor Off unless pm2 link Outbound only; keys are secrets

8. The Programmatic API

require('pm2') returns the same API class the CLI uses, with a callback-style interface. Type definitions ship in types/index.d.ts.

const pm2 = require('pm2');

pm2.connect((err) => {
  if (err) { console.error(err); process.exit(2); }

  pm2.start({
    script: 'api.js',
    name: 'api',
    instances: 'max',
    exec_mode: 'cluster',
    max_memory_restart: '300M'
  }, (err, apps) => {
    pm2.disconnect();            // you must disconnect explicitly
    if (err) throw err;
  });
});

Core exported functions:

Function Purpose
connect([noDaemon], cb) Connect to or launch the daemon
disconnect() Close the connection (required, even after killDaemon)
start(options|script|jsonFile, [options], cb) Start an app
stop(proc, cb) / restart(proc, cb) Lifecycle control
reload(proc, [{updateEnv}], cb) Zero-downtime reload
delete(proc, cb) Remove from the list
list(cb) Get all process descriptions
describe(proc, cb) Detail one process
dump(cb) / reloadLogs(cb) / flush(proc, cb) Persistence & logs
launchBus(cb) Subscribe to the event bus (logs, process events)
sendDataToProcessId(id, packet, cb) Send structured IPC to a process
sendSignalToProcessName(signal, proc, cb) Send a signal by name
startup(platform, cb) Generate the boot hook
killDaemon(cb) Kill the daemon (then still disconnect)

Note

connect() launches a persistent daemon by default — your script can exit and the apps keep running. Pass connect(true, cb) (no-daemon mode) to tie PM2's life to your script's; the apps then die when your process exits.

The event bus (launchBus) is an axon Sub-Emitter delivering log:out, log:err, process:event, and custom process:msg packets — the foundation for building log shippers or custom dashboards.

8.1 The God RPC method registry

Internally, every client action maps to a named RPC method on the daemon (Client.executeRemote('<method>', payload, cb)). The full registry lives across lib/God/*.js. Notable methods:

RPC method Action
prepare Resolve config and launch a new app
startProcessId / stopProcessId Start/stop a known process by id
restartProcessId Stop-and-start by id (carries new env)
reloadProcessId / softReloadProcessId Zero-downtime reload variants
deleteProcessId Stop and forget
getMonitorData The process list with CPU/memory (via pidusage)
getSystemData Host system metrics
resetMetaProcessId Reset restart counters
sendSignalToProcessId / ...Name Deliver OS signals
sendLineToStdin / sendDataToProcessId IPC to fork-mode processes
msgProcess Trigger a registered custom action (@pm2/io)
dumpProcessList Persist the list to dump.pm2
getReport The pm2 report payload

9. Process Status Model

A managed process moves through a fixed set of states (defined in constants.js):

Status Meaning
launching Spawned, not yet confirmed online
online Running normally
stopping Stop signal sent, awaiting exit
stopped Stopped, still in the list
errored Crashed and exhausted its restarts
waiting restart Scheduled to restart after a delay
one-launch-status One-shot launch tracking

PM2 tracks restart_time, unstable_restarts, created_at, and prev_restart_delay per process to detect flapping. Counters reset via resetState on a successful restart/reload and via pm2 reset.


10. Code Quality, Conventions & Internals

  • Language style — Pre-ES-module CommonJS throughout (require/module.exports), 'use strict', predominantly var and callback-style async (with async library helpers eachLimit/series/forEachLimit). The God methods use a mixin pattern: module.exports = function(God) { God.method = ... }, attaching RPC handlers onto a shared God object.
  • Concurrency — Bulk operations fan out at CONCURRENT_ACTIONS (default 2), raised to 10 for deletes. Atomicity for the saved list comes from write-then-backup, not locks.
  • The clusters_db registry — The daemon's single source of truth is an in-memory object keyed by pm_id. Temporary _old_<id> keys hold draining workers during reload.
  • Testing — Mocha-based, split into unit (test/unit.sh), e2e (test/e2e.sh), and Docker-parallel suites; vendored modules have their own suites (pm2-axon, pm2-axon-rpc, pm2-io-agent, pm2-io-bpm). A Dockerfile parameterizes RUNTIME=node|bun and NODE_VERSION for matrix testing.
  • Cross-platform — Windows support threads through IS_WINDOWS checks: named pipes instead of Unix sockets, windowsHide on spawned processes, and home-directory quirks (see issue #6106).

10.1 Repository layout

pm2/
├── index.js                 Programmatic entry (exports the API)
├── constants.js             Tunable constants (env-overridable)
├── paths.js                 ~/.pm2 file structure
├── bin/                     pm2, pm2-dev, pm2-runtime, pm2-docker
├── lib/
│   ├── API.js               Client/API class
│   ├── Client.js            Daemon connection + RPC
│   ├── Daemon.js            Daemon bootstrap
│   ├── God.js, God/         The process manager
│   ├── Common.js            Config parsing & validation
│   ├── ProcessContainer*.js Per-process host wrappers
│   ├── API/                 Startup, Serve, Log, Modules, Deploy, UX
│   ├── binaries/            The four CLI front-ends
│   └── templates/           Init scripts, Dockerfiles, sample apps
├── modules/
│   ├── pm2-axon, pm2-axon-rpc   Socket transport / RPC
│   ├── pm2-io-agent             PM2+ interactor daemon
│   └── pm2-io-bpm               @pm2/io probes
├── types/index.d.ts         Programmatic API types
└── test/                    unit / e2e / programmatic suites

11. Key Dependencies

Dependency Version Role in PM2
commander 2.15.1 CLI parsing for all four binaries
chokidar 3.6.0 File watching (--watch)
async 3.2.6 Concurrency helpers (eachLimit, series)
pidusage 4.0.1 Per-process CPU/memory sampling
croner 4.1.97 Cron-based scheduled restarts
dayjs 1.11.15 Log timestamp formatting
ws 8.20.0 WebSocket transport (PM2+ agent)
js-yaml 4.1.1 YAML ecosystem files
vizion 2.2.1 Git metadata (revision tracking)
pm2-deploy 1.0.2 SSH deploy system (pm2 deploy)
@pm2/js-api 0.8.0 PM2+ API client
proxy-agent 6.5.0 Outbound proxy support for the agent
semver 7.7.2 Version checks (incl. the Bun guard)
@pm2/blessed, cli-tableau, ansis — Terminal UI / tables / colors
pm2-sysmonit (optional) ^1.2.8 Host vitals (pm2 set pm2:sysmonit true)

Note

vizion's git parsing is gated off by default (ENABLE_GIT_PARSING = false in constants.js) for performance; --no-vizion disables it explicitly per app.


12. Troubleshooting (Operator Edition)

Error: Init system not found / Unknown platform / init system name

Cause — pm2 startup could not detect a supported init system.

Solution — Name it explicitly: pm2 startup systemd (or upstart, openrc, launchd, …). Run the printed sudo command.

Prevention — Standardize on systemd across your fleet and pin the platform in your provisioning scripts.


Error: You cannot set a pm2_home and independent instance in same time

Cause — The programmatic API was constructed with both pm2_home and independent: true.

Solution — Choose one: set pm2_home for a fixed location, or independent: true for an auto-generated throwaway instance.


Problem: Two PM2 deployments interfering on one host

Cause — They share the default ~/.pm2, so they share one daemon and one process list.

Solution / Prevention — Give each a distinct PM2_HOME. Confirm with pm2 report (it prints the active home).


Problem: The HTTP API on port 9615 is exposed externally

Cause — --web / pm2 web was enabled and binds 0.0.0.0 by default, leaking process metadata and env vars.

Solution — Disable it, or set PM2_API_IPADDR=127.0.0.1 and PM2_WEB_STRIP_ENV_VARS=true. Firewall the port.

Prevention — Treat 9615 as internal-only; never expose it through a load balancer.


Problem: pm2 update after upgrade restarts apps unexpectedly

Cause — pm2 update necessarily kills and relaunches the daemon, briefly cycling all apps. This is expected.

Solution — Schedule pm2 update during a maintenance window, or roll it host-by-host behind a load balancer.

Prevention — Use cluster mode so the post-update resurrection brings instances back load-balanced.


Problem: Interpreter-dependent apps break after a PM2 major upgrade

Cause — Changes to interpreter resolution between major versions (the issue tracker shows Python/bundle interpreter regressions around 6.x → 7.x).

Solution — Pin the interpreter explicitly in the ecosystem file and verify with pm2 describe.

Prevention — Test a canary host before fleet-wide PM2 major upgrades; keep interpreters on a stable PATH.


13. Potential Improvements & Technical Debt

These observations come from reading the source and may guide contributors:

  • Legacy JavaScript idioms — The codebase predates ES modules and async/await; var, callback pyramids, and the async library dominate. A gradual migration to const/let and promises would improve readability and reduce callback-error-handling bugs.
  • lib/API.js size — At ≈2,600 lines the API class mixes CLI concerns, RPC orchestration, and output formatting. Splitting lifecycle, listing/inspection, and module management into separate mixins (mirroring the lib/God/* structure) would aid maintainability.
  • HTTP API security defaults — The process API binds 0.0.0.0 and includes env vars unless explicitly stripped. A safer default (127.0.0.1, env stripped) would reduce accidental exposure.
  • Duplicate flush command registration — lib/binaries/CLI.js registers flush [api] with an older flush block left commented out; a small cleanup.
  • Cross-platform socket handling — Windows named-pipe paths are hard-coded (a @todo notes they should incorporate PM2_HOME/uid), which blocks fully isolated multi-instance setups on Windows.

14. Quick Reference

Daemon control

pm2 ping                 # is the daemon alive?
pm2 kill                 # stop daemon + all apps
pm2 update               # reload daemon after package upgrade
pm2 report               # full diagnostic dump

Persistence

pm2 save                 # write dump.pm2
pm2 resurrect            # restore from dump.pm2
pm2 startup [platform]   # generate boot hook
pm2 unstartup            # remove boot hook

Key paths

~/.pm2/dump.pm2          saved process list
~/.pm2/logs/             application logs
~/.pm2/rpc.sock          daemon control socket
~/.pm2/pub.sock          event bus socket
~/.pm2/pm2.log           daemon log

Key tunables

PM2_HOME                 state directory
PM2_KILL_TIMEOUT=1600    SIGINT→SIGKILL grace (ms)
PM2_CONCURRENT_ACTIONS=2 parallel action fan-out
PM2_API_PORT=9615        HTTP process API port
PM2_GRACEFUL_TIMEOUT=8000 soft-reload disconnect grace (ms)