You SSH in, run node app.js, everything works. Then you close the terminal and the site goes down. Or the VPS reboots after a kernel update and your app just... doesn't come back. If that sounds familiar, you're running Node the way you'd run a script, not the way you'd run a service. PM2 fixes that in about five minutes.
This is one of the most common tickets we see from customers who've moved off shared hosting onto a VPS to run a custom Node app — an API backend, a Next.js server, a Discord bot, whatever. It works fine until the terminal closes, and then it's a mystery.
Symptom
A few variations of the same underlying problem:
- App works while you're SSH'd in, dies the moment you disconnect
- Site goes down after every server reboot and has to be started manually
- App crashes on an unhandled exception and stays down until someone notices
- Multiple deploys leave several stray
nodeprocesses running on the same port, causingEADDRINUSEerrors
Cause
Running node app.js directly attaches the process to your shell session. When the session ends (SSH disconnects, terminal closes, nohup wasn't used), the process gets a hangup signal and dies with it. Even if you dodge that with nohup or screen, none of those approaches restart the app automatically on a crash or bring it back after a reboot — you still have to babysit it by hand.
What you actually want is a process manager: something that runs as a daemon, independent of any SSH session, watches the app, restarts it if it dies, and can hook into systemd so it starts automatically at boot. That's exactly what PM2 does, and it's the de facto standard for this in the Node ecosystem.
Fix
1. Install PM2 globally
SSH into your VPS and install it via npm (Node itself needs to already be installed — via NodeSource or nvm, whichever you used originally):
npm install -g pm22. Start your app under PM2
From your app's directory:
cd /home/youruser/myapp
pm2 start app.js --name myappThe --name flag matters once you're running more than one app — it's how you'll refer to it in every command afterward instead of hunting for a process ID.
For apps with a specific start script (common with frameworks like Next.js or NestJS), point PM2 at npm instead:
pm2 start npm --name myapp -- run start3. Confirm it's running
pm2 status
pm2 logs myapppm2 status shows every managed process, its uptime, restart count, and memory usage in one table. pm2 logs tails stdout/stderr live — this is your first stop when something's misbehaving, since PM2 captures output that would otherwise vanish the moment a plain node process dies.
4. Make it survive a reboot
This is the step people skip, and it's the one that actually matters for a production VPS. By default, PM2's process list only lives in memory — a reboot wipes it clean.
pm2 startupThis prints a systemd command tailored to your user and OS. Copy that exact line, run it (usually needs sudo), then save your current process list so it gets restored on boot:
pm2 saveFrom now on, a reboot brings your app back automatically, no manual restart needed.
5. Test the crash-recovery behavior
Don't just assume it works — kill the process and watch PM2 bring it back:
pm2 list
kill -9 <pid>
pm2 listYou should see the restart count tick up and the app back in online status within a second or two.
Running multiple apps or an ecosystem file
Once you've got more than one app, or you're deploying to staging and production from the same repo, an ecosystem file beats typing flags every time. Create ecosystem.config.js in your project root:
module.exports = {
apps: [{
name: 'myapp',
script: 'app.js',
instances: 2,
exec_mode: 'cluster',
env: {
NODE_ENV: 'production',
PORT: 3000
},
max_memory_restart: '300M'
}]
}Then start everything with a single command:
pm2 start ecosystem.config.jsexec_mode: 'cluster' with instances: 2 spins up two copies of your app across CPU cores and load-balances between them internally — useful if your VPS has more than one vCPU and the app is CPU-bound. max_memory_restart auto-restarts a process if it balloons past a memory threshold, which is a decent safety net for a slow leak you haven't found yet.
Putting Nginx in front
PM2 manages the Node process; it doesn't serve HTTPS or handle your domain. Pair it with Nginx as a reverse proxy pointing at the port PM2's app is listening on (commonly localhost:3000), and let Nginx handle SSL termination via Let's Encrypt. If you haven't set that up yet, that's a separate piece of config — the short version is a proxy_pass http://127.0.0.1:3000; block in your server config, plus a certbot run for the certificate.
Prevention
| Practice | Why it matters |
|---|---|
Always run pm2 save after starting or stopping apps | Otherwise pm2 startup won't know what to restore on reboot |
Set up log rotation with pm2 install pm2-logrotate | PM2 logs grow forever by default and can quietly fill your disk |
Use max_memory_restart on memory-hungry apps | Catches leaks before they trigger the VPS's own OOM killer |
Check pm2 monit periodically | Live CPU/memory view per process, useful before things get bad |
Deploy via a script that runs pm2 reload, not restart | Reload does a zero-downtime restart in cluster mode; restart drops connections |
One more thing worth doing on a fresh VPS: run pm2 update occasionally to keep the daemon itself current, and check pm2 --version against what's in your deploy scripts so you're not troubleshooting a version mismatch six months from now.
Frequently Asked Questions
Do I need root access to use PM2?
No, you can install and run PM2 as a regular user with npm's global prefix set to a user-writable path, or with sudo npm install -g pm2 for a system-wide install. The pm2 startup step does need sudo once, to register the systemd service.
What's the difference between PM2 and just using systemd directly?
You can absolutely write a raw systemd unit file for a Node app, and some teams prefer that for consistency with the rest of their stack. PM2 adds a layer on top that's Node-specific: built-in log management, cluster mode load balancing, zero-downtime reloads, and a much friendlier CLI for day-to-day work. Under the hood, pm2 startup actually generates a systemd service for you anyway.
My app keeps restarting in a loop — what do I check?
Run pm2 logs myapp --lines 100 first; it's almost always an uncaught exception on startup, a missing environment variable, or the port already being in use by a stray process from a previous deploy. If the restart count in pm2 status is climbing fast, PM2 will eventually back off and mark it errored rather than restart forever.
Can PM2 manage apps other than Node, like Python or a static binary?
Yes. PM2 can run any executable with pm2 start ./script.py --interpreter python3 or manage a compiled binary directly. It's most commonly used for Node, but the process-supervision features aren't Node-specific.
How do I deploy an update without downtime?
Pull your new code, then run pm2 reload myapp instead of pm2 restart myapp. In cluster mode, reload restarts instances one at a time so there's always at least one handling traffic. In fork mode (a single instance), there's a brief gap either way — that's the trade-off for not needing multiple CPU cores.
