# PM2 Administrator & Architecture Guide

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_id`s 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:

```bash
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:

```bash
# 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.

```dockerfile
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

```bash
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:

```bash
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:

```bash
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

```bash
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`:

```bash
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`.

```javascript
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

```bash
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

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

### Key paths

```text
~/.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

```text
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)
```

---

_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. Unofficial; not affiliated with or reviewed by the project._
