Skip to content
Products
AI Website Builder New VPS Hosting Cloud Servers Web Hosting Dedicated Servers Domains
Company
About Documentation Support Center Contact Get Started Login Client Area
ALL SYSTEMS OPERATIONAL
VPS

Keep a Node.js App Running on a VPS: PM2 Setup Guide

Getwebup 5 min read

You SSH into your VPS, run node server.js, and everything works. Close the terminal to grab a coffee, come back, and the site's down. Or worse — it runs fine for three days, then a bad request crashes it at 2 a.m. and it never comes back up on its own. Both problems have the same root cause, and the same fix.

Symptom: the app dies when you're not watching it

This shows up in a few different ways depending on what killed it:

  • The site works while you're SSH'd in, but goes down the second you disconnect (even with the process seemingly still "running" in a screen you forgot about).
  • A memory leak or an unhandled promise rejection crashes the process, and nothing restarts it. Visitors get ERR_CONNECTION_REFUSED until you notice and manually restart.
  • You reboot the VPS for a kernel update, and the app just doesn't come back — because it was never actually configured to start on boot.

Cause: node.js has no built-in process supervision

Running node app.js directly starts a single foreground process attached to your shell. When your SSH session ends, the shell sends a SIGHUP to its child processes, and Node exits with it — there's no daemon behavior, no auto-restart, no boot persistence built in. Node was never designed to babysit itself; that job belongs to a process manager sitting on top of it.

This is different from a typical cPanel "Setup Node.js App" account, where WHM/cPanel wires the app into Apache's mod_passenger for you. On a bare VPS you're managing the whole stack yourself, so you need your own supervisor. PM2 is the most common choice because it's purpose-built for Node, but the same problem could be solved with systemd or Docker — more on that trade-off in the FAQ below.

Fix: install PM2 and hand it your app

1. Install PM2 globally

sudo npm install -g pm2

Check it installed correctly:

pm2 --version

2. Start your app under PM2 instead of node

cd /var/www/myapp
pm2 start server.js --name myapp

For apps started via npm start:

pm2 start npm --name myapp -- start

Confirm it's running:

pm2 list

You'll see a table with the app name, status (online), CPU, memory, and restart count. That restart count matters — if it's climbing on its own, something's crashing repeatedly and you've got a separate bug to chase.

3. Make it survive a reboot

This is the step most people skip, and it's the one that actually fixes the "didn't come back after a server restart" problem:

pm2 startup

PM2 prints a command tailored to your OS and user — copy and run that exact line (it registers a systemd service). Then save the current process list so it knows what to relaunch:

pm2 save

From now on, a reboot brings your app back automatically, with no manual SSH-and-restart required.

4. Use an ecosystem file for anything beyond one simple app

Once you've got more than one app, or need environment variables, create ecosystem.config.js in your project root:

module.exports = {
  apps: [
    {
      name: "myapp",
      script: "server.js",
      instances: 2,
      exec_mode: "cluster",
      env: {
        NODE_ENV: "production",
        PORT: 3000
      },
      max_memory_restart: "300M"
    }
  ]
}

Start everything defined in the file with:

pm2 start ecosystem.config.js

exec_mode: "cluster" with instances: 2 runs two copies of your app across CPU cores and load-balances between them — useful once a single process can't keep up, and it also means one crashed worker doesn't take the whole site down. max_memory_restart auto-restarts a worker if it balloons past a memory ceiling, which is a cheap safety net against slow leaks.

5. Put Nginx in front of it

PM2 keeps the Node process alive; it doesn't serve HTTPS or handle your domain. A minimal reverse proxy block:

server {
    listen 80;
    server_name example.com;

    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_cache_bypass $http_upgrade;
    }
}

Then issue a certificate with Certbot and let it handle the TLS termination — Node itself should stay on plain HTTP behind the proxy.

6. Deploy updates without downtime

Once you're in cluster mode, ship code changes with a reload instead of a restart:

pm2 reload myapp

reload replaces workers one at a time, so there's always at least one instance serving traffic. Plain pm2 restart kills everything first, which means a brief gap — fine for a single-instance app, not what you want for anything customer-facing.

CommandWhat it doesWhen to use it
pm2 restart myappStops then starts the processSingle instance, brief downtime is fine
pm2 reload myappRolling restart, zero downtimeCluster mode, production deploys
pm2 logs myappTails stdout/stderrDebugging a crash right after it happens
pm2 monitLive CPU/memory dashboardChecking if a leak is building up
pm2 flushClears log filesLogs are eating disk space

Prevention: don't wait for the next crash to notice

  • Set up log rotation. PM2 logs grow forever by default. Install the rotate module: pm2 install pm2-logrotate, then it'll cap file size and keep a rolling number of days automatically.
  • Run PM2 as a non-root user. Create a dedicated app user, run pm2 startup under that account, and keep your Node process off root privileges entirely.
  • Watch the restart count, not just uptime. A process showing "online" with 40 restarts in the last hour is not healthy — it's crash-looping and PM2 is just quietly catching it every time.
  • Re-run pm2 save after any change to the process list — adding an app, changing its name, or switching to cluster mode. If you forget, a reboot restores the old, outdated list.

Frequently asked questions

Do I still need PM2 if I'm already using systemd?

You can run Node directly under a systemd unit instead of PM2 and get restart-on-crash and boot persistence too. PM2 is worth it on top of that when you want cluster mode, zero-downtime reloads, and a single command (`pm2 logs`, `pm2 monit`) to manage multiple Node apps without writing a separate unit file for each one.

PM2 shows my app as 'online' but the site is still down. What's wrong?

PM2 only guarantees the Node process is running, not that it's listening on the right port or that Nginx is correctly proxying to it. Run `pm2 logs myapp` to check for startup errors, then confirm the port in your Nginx `proxy_pass` matches what your app actually binds to.

I get 'Error: listen EADDRINUSE' after restarting with PM2. How do I fix it?

This means an old instance of your app is still holding the port, usually a leftover process started outside PM2. Run `sudo lsof -i :3000` (swap in your port) to find the PID, kill it, then start the app fresh under PM2 so it's the only thing managing that port going forward.

Can I run several different Node.js apps on one VPS with PM2?

Yes. Give each app a distinct `name` and `PORT` in its ecosystem config, then point a separate Nginx server block (with its own domain or subdomain) at each port. `pm2 list` will show all of them, and you can restart or reload each independently.

Should I use PM2 or Docker to manage my Node.js app?

PM2 is lighter and faster to set up when you're deploying directly onto a VPS you fully control. Docker makes more sense once you need to package dependencies with the app, run identical environments across staging and production, or move the app between servers easily — the two aren't mutually exclusive; some teams run PM2 inside a container.

#pm2 #nodejs #vps #process-manager #nginx-reverse-proxy #zero-downtime

Keep reading

Chat with Support