Managing Node Apps with PM2
When you run a Node.js app with node app.js, it dies the moment you close your terminal, and if the app crashes it stays dead. A process manager (a program whose job is to launch your app, watch it, and restart it if it stops) fixes this. PM2 is the most popular process manager for Node.js: it keeps your app running forever, restarts it on crashes, runs multiple copies to use every CPU core, and survives server reboots. This page shows you how to use it on Ubuntu 22.04/24.04 LTS.
When to use PM2 (and when not to)
Use PM2 when you are deploying a Node.js (or any long-running JavaScript) application and you want crash recovery, easy log access, and the ability to scale across CPU cores with almost no configuration. It is friendly, fast to set up, and great for small-to-medium servers where you manage everything yourself.
Reach for systemd instead (the init system built into Ubuntu that supervises services) when you want a single, OS-native way to manage all services on the box — Node apps, databases, your own scripts — under one tool, with tighter security controls. We compare the two near the end.
PM2 is for long-running processes (web servers, workers, queues). Do not use it for short scripts that run once and exit — for those, use
cronor a systemd timer.
Installing PM2
PM2 is an npm package, so you need Node.js first. Install it globally so the pm2 command is available everywhere.
node --version
sudo npm install -g pm2
pm2 --version
Output:
v20.11.1
added 183 packages in 8s
5.4.2
Starting and listing apps
The core command is pm2 start. Point it at your app’s entry file. The --name flag gives the process a readable label so you can refer to it later.
cd /var/www/myapp
pm2 start app.js --name myapp
Output:
[PM2] Starting /var/www/myapp/app.js in fork_mode (1 instance)
[PM2] Done.
┌────┬─────────┬─────────┬───────┬────────┬──────────┐
│ id │ name │ status │ ↺ │ cpu │ memory │
├────┼─────────┼─────────┼───────┼────────┼──────────┤
│ 0 │ myapp │ online │ 0 │ 0% │ 48.2mb │
└────┴─────────┴─────────┴───────┴────────┴──────────┘
See everything PM2 is managing at any time with pm2 list (or pm2 ls). The ↺ column counts how many times PM2 has restarted the app — a fast-rising number means your app keeps crashing.
pm2 list
Reading logs
PM2 captures everything your app prints to the screen. Stream live logs with pm2 logs, or target one app by name.
pm2 logs myapp --lines 50
Output:
/root/.pm2/logs/myapp-out.log last 50 lines:
0|myapp | Server listening on http://127.0.0.1:3000
0|myapp | GET /health 200 4ms
/root/.pm2/logs/myapp-error.log last 50 lines:
0|myapp | (no errors)
Press Ctrl+C to stop following. The raw log files live in ~/.pm2/logs/. Over time these grow large — install the log rotation module so they get capped and compressed automatically.
pm2 install pm2-logrotate
pm2 set pm2-logrotate:max_size 10M
pm2 set pm2-logrotate:retain 7
Restart, reload, stop, delete
These are the commands you use day to day. Restart stops and starts the app (a brief moment of downtime). Reload restarts workers one at a time with zero downtime — but only works in cluster mode (see below).
pm2 restart myapp # full restart (short downtime)
pm2 reload myapp # zero-downtime restart (cluster mode)
pm2 stop myapp # stop but keep it in the list
pm2 delete myapp # stop and remove from the list
Output:
[PM2] Applying action restartProcessId on app [myapp](ids: [ 0 ])
[PM2] [myapp](0) ✓
Cluster mode — using every CPU core
A single Node.js process runs on one CPU core only. If your server has 4 cores, a plain app wastes 3 of them. Cluster mode launches several identical copies of your app and load-balances incoming connections across them, so you use the whole machine. Pass -i with the number of instances, or -i max to match the core count automatically.
pm2 delete myapp
pm2 start app.js --name myapp -i max
pm2 list
Output:
[PM2] Starting /var/www/myapp/app.js in cluster_mode (4 instances)
[PM2] Done.
┌────┬─────────┬──────────────┬──────────┬────────┬──────────┐
│ id │ name │ mode │ status │ cpu │ memory │
├────┼─────────┼──────────────┼──────────┼────────┼──────────┤
│ 0 │ myapp │ cluster │ online │ 0% │ 51.1mb │
│ 1 │ myapp │ cluster │ online │ 0% │ 50.8mb │
│ 2 │ myapp │ cluster │ online │ 0% │ 50.9mb │
│ 3 │ myapp │ cluster │ online │ 0% │ 51.0mb │
└────┴─────────┴──────────────┴──────────┴────────┴──────────┘
Cluster mode only works if your app does not hold state in memory between requests (no in-memory sessions or counters). Each instance is separate, so store shared state in Redis or your database, otherwise users will see inconsistent results.
Using an ecosystem config file
Typing flags every time is error-prone. PM2 reads a config file (the “ecosystem file”) so your settings live in version control. Generate a starter and edit it.
pm2 ecosystem
This creates ecosystem.config.js:
module.exports = {
apps: [{
name: "myapp",
script: "./app.js",
instances: "max",
exec_mode: "cluster",
env: {
NODE_ENV: "production",
PORT: 3000
}
}]
};
Start everything defined in it with:
pm2 start ecosystem.config.js
Boot persistence — startup + save
By default PM2 forgets everything when the server reboots. Two commands fix this. pm2 startup generates and installs a systemd service that launches PM2 on boot. pm2 save snapshots the current process list so PM2 knows what to bring back.
pm2 startup systemd
Output:
[PM2] To setup the Startup Script, copy/paste the following command:
sudo env PATH=$PATH:/usr/bin pm2 startup systemd -u ubuntu --hp /home/ubuntu
Copy and run the exact sudo line it prints (the user and path differ per machine). Then save your current apps:
sudo env PATH=$PATH:/usr/bin pm2 startup systemd -u ubuntu --hp /home/ubuntu
pm2 save
Output:
[PM2] Saving current process list...
[PM2] Successfully saved in /home/ubuntu/.pm2/dump.pm2
Now reboot to confirm your app comes back automatically: sudo reboot, then after logging back in, run pm2 list.
Monitoring
For a live dashboard of CPU, memory, and logs right in your terminal, use pm2 monit.
pm2 monit
For a one-time snapshot of a single app’s full details (uptime, restart count, log paths, memory), use pm2 show.
pm2 show myapp
PM2 also offers pm2 plus, a hosted web dashboard, if you want graphs and alerts without building your own.
PM2 vs systemd for Node
| Aspect | PM2 | systemd |
|---|---|---|
| Setup effort | Very low (pm2 start) | Write a .service unit file |
| Cluster / multi-core | Built in (-i max) | Manual (one unit per port + Nginx balancing) |
| Log access | pm2 logs, built-in rotation | journalctl -u app, system journal |
| Zero-downtime reload | Yes (pm2 reload) | Needs socket activation or extra tooling |
| Manages non-Node services | No | Yes — everything on the box |
| Security sandboxing | Limited | Strong (User=, ProtectSystem, etc.) |
| Best for | Node-focused servers, quick deploys | Mixed fleets, hardened production |
A common pattern: use PM2 in development and small projects, and graduate to systemd for hardened production servers — see the Node systemd service page for that setup.
Best Practices
- Always run
pm2 saveafter changing your running apps, or a reboot will bring back the old list. - Use an
ecosystem.config.jschecked into Git instead of long command-line flags, so config is reproducible. - Run in cluster mode (
-i max) only after confirming your app is stateless, and put shared state in Redis or your database. - Install
pm2-logrotateon day one so logs cannot fill up/var/logand the disk. - Set
NODE_ENV=productionvia the ecosystem file’senvblock — many libraries run faster and log less in production mode. - Put Nginx in front of PM2 as a reverse proxy for TLS and a public port — never expose Node directly on port 3000.
- Pin the PM2 version (
npm install -g pm2@5) in your provisioning scripts so deploys are predictable.