Diagnosing PM2 Crash Loops and Unexpected Restarts in Node.js Applications
A diagnostic guide for PM2-managed Node.js apps stuck in crash loops: recognize the symptoms, correlate logs with restart counts, and apply targeted fixes for exceptions, port conflicts, OOM kills, and stale environments.
04 Aug 2026, 02:13 UTC

When PM2 restarts become a crash loop
PM2 restarts a process whenever it exits unless you explicitly stop it. If your Node.js application exits repeatedly, pm2 list shows the restart count climbing and the status flapping between online and errored. The useful takeaway: a crash loop is a symptom of the application exiting or being killed, not a PM2 malfunction. Diagnose the exit cause first, then adjust the restart policy.
Recognizable condition
pm2 listshows a risingrestartcount and status changing betweenonline,errored, orstopped.pm2 logs <app> --errrepeats the same stack trace or error message in a tight loop.pm2 describe <app>reports an uptime measured in seconds rather than hours, with a high restart count.
Common causes and where to look
| PM2 signal | Likely cause | Confirm with |
|---|---|---|
| Repeated stack trace in stderr | Uncaught exception or unhandled promise rejection in the app | pm2 logs <app> --lines 200 --err |
EADDRINUSE in logs | Two processes binding the same port, often outside cluster mode | pm2 describe <app> exec mode plus app logs |
| No app error, restart count rising, memory near ceiling | OS or container OOM kill | System OOM logs or container kill events; compare with pm2 describe <app> memory trend |
| Clean exit after deploy or Node upgrade | Missing or stale environment variables and code paths | pm2 describe <app> environment and pm2 reload <app> --update-env |
| Gradual memory growth then restart | Leak being masked by max_memory_restart | pm2 describe <app> memory field over successive checks |
Ordered checks
Run these on the host where the PM2 daemon runs, as the user that owns the process (or with sudo if PM2 was started by root). Replace <app> with the PM2 process name or ID from pm2 list.
1. Record exact versions
PM2 CLI flags and defaults have changed across major versions. Record pm2 --version and node --version before applying version-sensitive options. If a flag is unfamiliar, confirm it with pm2 <command> --help.
2. Inspect the process state
Run pm2 list to see status and restart count. Then run pm2 describe <app> to inspect exec mode, script path, restart time, uptime, memory, and the effective restart policy fields (autorestart, max_restarts, min_uptime, restart_delay, max_memory_restart, exp_backoff). Expected check: uptime should be stable and the restart count should stop rising once the cause is fixed.
3. Correlate logs with restart timestamps
In a second terminal, tail pm2 logs <app> --lines 200 --err. Look for the last lines before each restart. If the same application error appears immediately before every exit, the cause is in the application, not the process manager.
4. Check exec mode and port binding
pm2 describe <app> shows exec mode as fork or cluster. In fork mode, each instance is an independent process; if multiple instances bind the same port, you will see EADDRINUSE. In cluster mode, PM2 uses the Node cluster module to share the port. Expected check: the number of listening workers should match the intended instance count without address-in-use errors.
5. Check memory and system signals
Compare the memory value in pm2 describe <app> over several minutes with system OOM killer messages or container kill events. PM2 may report a clean exit while the OS OOM killer terminates the worker; PM2 logs alone cannot prove a kernel kill. Treat PM2 memory as a signal, not proof.
6. Check disk and log growth
Large unrotated logs can fill the disk and destabilize workers. Check the size of PM2 log files (often under ~/.pm2/logs/). If they are growing without bound, plan rotation with the separate pm2-logrotate module or OS-level rotation.
Fixes tied to the findings
Application exceptions
Fix the uncaught exception or unhandled rejection in application code. Changing autorestart only controls how PM2 reacts to the exit; it does not stop the exit. If you must buy time, set autorestart: false deliberately, but understand the process will stay down after a crash.
Port conflicts
If you need multiple workers on one port, use cluster mode (exec_mode: 'cluster', instances: <n>). For zero-downtime worker replacement in cluster mode, prefer pm2 reload <app> over pm2 restart <app>. Risk: in fork mode, pm2 reload behaves like a restart and can cause downtime.
OOM kills
Fix the underlying leak. max_memory_restart is a mitigation, not a fix; it can hide a growing heap trend until the next restart. If you set it, choose a value informed by your container or host memory limit, not a guess.
Stale environment after upgrades
After a Node upgrade or environment change, run pm2 reload <app> --update-env to refresh the process environment and code path. Expected check: pm2 describe <app> should show the updated Node path and environment variables.
Restart policy tuning
If a crash-looping app is marked errored too quickly, adjust the ecosystem file. A low min_uptime with a low max_restarts can exhaust retries in seconds. Example configuration:
module.exports = {
apps: [{
name: 'api',
script: './server.js',
exec_mode: 'cluster',
instances: 2,
autorestart: true,
max_restarts: 10,
min_uptime: 5000,
restart_delay: 2000,
exp_backoff: true,
max_memory_restart: '500M'
}]
}
After editing, apply with pm2 reload ecosystem.config.js --env production --update-env (or pm2 start if the app is not running). Risk: reloading in cluster mode replaces workers gracefully; reloading in fork mode restarts the process. Confirm with pm2 describe <app> that the intended values are active.
Log rotation
Install and configure pm2-logrotate or use OS-level rotation. Expected check: log file sizes stop growing monotonically and disk usage stabilizes.
Escalation criteria
Escalate to a senior engineer or platform team when:
- PM2 reports a clean exit but system or container logs show an OOM kill, and the memory trend in
pm2 describedoes not explain it. - Cluster-mode workers repeatedly fail to listen or handshake after
pm2 reload, suggesting a load-balancer or networking issue outside the app. - Ecosystem changes do not appear in
pm2 describeafter a reload, indicating a stale daemon or a bad ecosystem file thatpm2 savewill not repair. - Disk pressure from logs persists after rotation is configured, pointing to an infrastructure problem rather than an app problem.
Limitations and verification
PM2 CLI behavior varies by major version. Confirm flags with pm2 --version and pm2 <command> --help. pm2 save persists the current process list, but it does not repair a bad ecosystem file or guarantee intended configuration after manual edits. To verify a fix, reproduce the original failure with pm2 start ecosystem.config.js --env <env>, tail pm2 logs <app> --lines 200 --err, and confirm the restart count in pm2 list stops rising.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.