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
pm2npm package does not upgrade the running daemon — you must runpm2 updateto 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:
- CLI parse —
lib/binaries/CLI.js(built oncommander) parses global options and therestartsub-command, then instantiates the API class (lib/API.js) and callspm2.connect(). - Connect —
lib/Client.jschecks whether the daemon is alive (by pingingrpc.sockin~/.pm2). If not, it forks and detaches a new daemon process, then opens an axon-rpc connection. - Client-side orchestration —
API.restart()resolves the target (api) to one or morepm_ids by querying the daemon, then callsClient.executeRemote('restartProcessId', {id, env}, cb)for each. - Daemon action — In the daemon,
God.restartProcessId(lib/God/ActionMethods.js) looks up the process inclusters_db, stops it (SIGINT → SIGKILL afterkill_timeout), and re-executes it viaGod.executeApp, which dispatches toClusterModeorForkMode. - Event broadcast — The daemon emits process events (
process:event,log:out,log:err) on its internal bus, published overpub.sockso subscribers (e.g.pm2 logs, the PM2+ agent) receive them. - 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 unpacksprocess.env.pm2_envback into a structured object. This is why each instance receives apm2_envvariable.
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 defaultreload): the old worker is renamed to a temporary_old_<id>key, a new worker is spawned, and PM2 waits for the new worker'slisteningevent (or areadyIPC message ifwait_readyis set). On success — or afterlisten_timeout/GRACEFUL_LISTEN_TIMEOUT(3000ms) — the old worker is deleted.softReload(startOrGracefulReload): similar, but the old worker is first asked to shut down via ashutdownIPC message and givenGRACEFUL_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:
- If
KILL_USE_MESSAGEorshutdown_with_messageis set, send ashutdownIPC message first. - Otherwise send
KILL_SIGNAL(defaultSIGINT). Withtreekillenabled (the default), the entire process tree is signalled; with--no-treekill, only the main process. God.processIsDeadpolls the process tree. If anything is still alive afterkill_timeout(default 1600ms), PM2 escalates toSIGKILL.
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/reloaddo not refresh the environment — a deliberate safety feature. CI pipelines that change env vars must pass--update-envor 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 aspm2:sysmonit), runpm2 updateso 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 startuprun and the printed sudo command executed - [ ] Apps started in
clustermode for zero-downtime reloads - [ ]
max_memory_restartset as a leak guardrail - [ ]
pm2 saverun after the final app list is correct - [ ]
pm2-logrotateinstalled 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 usepm2-runtimein 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.dumpProcessListdeliberately 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 withpm2 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.sockandpub.socklive in~/.pm2and are protected by filesystem permissions. Anyone who can read/write them can fully control your processes. Keep~/.pm2owned by, and readable only by, the PM2 user (PM2_SOCKET_USER/PM2_SOCKET_GROUPcan adjust socket ownership). - The HTTP process API —
pm2 web/--webopens an HTTP server onPM2_API_PORT(9615), bound by default to0.0.0.0(PM2_API_IPADDR). It exposes process metadata including environment variables. Do not expose this port publicly. Bind it to127.0.0.1, or setPM2_WEB_STRIP_ENV_VARS=trueto redact env vars, or leave it disabled (the default). - The static file server —
pm2 servecan 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 tospawn). Run apps as the least-privileged user possible. pm2 startuptypically needssudoto 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. Passconnect(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', predominantlyvarand callback-style async (withasynclibrary helperseachLimit/series/forEachLimit). The God methods use a mixin pattern:module.exports = function(God) { God.method = ... }, attaching RPC handlers onto a sharedGodobject. - 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_dbregistry — The daemon's single source of truth is an in-memory object keyed bypm_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). ADockerfileparameterizesRUNTIME=node|bunandNODE_VERSIONfor matrix testing. - Cross-platform — Windows support threads through
IS_WINDOWSchecks: named pipes instead of Unix sockets,windowsHideon 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 = falseinconstants.js) for performance;--no-viziondisables 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 theasynclibrary dominate. A gradual migration toconst/letand promises would improve readability and reduce callback-error-handling bugs. lib/API.jssize — 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 thelib/God/*structure) would aid maintainability.- HTTP API security defaults — The process API binds
0.0.0.0and includes env vars unless explicitly stripped. A safer default (127.0.0.1, env stripped) would reduce accidental exposure. - Duplicate
flushcommand registration —lib/binaries/CLI.jsregistersflush [api]with an olderflushblock left commented out; a small cleanup. - Cross-platform socket handling — Windows named-pipe paths are hard-coded (a
@todonotes they should incorporatePM2_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)