Run a Node.js app on your VPS with PM2 and nginx

Starting a Node app in your SSH session works until you close the window. Then it stops. This guide sets it up properly: PM2 keeps it running and restarts it, and nginx sits in front so people can reach it on a normal address with HTTPS.

It assumes an Ubuntu or Debian VPS, Node.js already installed, and a domain pointed at the server. If the domain part isn't done, start with Point your domain to your server.

1. Get the app running by hand first

Upload your code, install its dependencies and start it once to make sure it works:

cd /var/www/myapp
npm ci --omit=dev
node server.js

Note the port it listens on, here 3000. Press Ctrl+C to stop it.

Make sure the app listens on 127.0.0.1 (localhost) and not on every address. Only nginx needs to reach it, and this stops people bypassing nginx by hitting port 3000 directly.

2. Keep it alive with PM2

sudo npm install -g pm2
pm2 start server.js --name myapp
pm2 save
pm2 startup

The last command prints another command, starting with sudo env PATH=.... Copy that line and run it. That's what makes your app start again after a reboot. Then run pm2 save once more.

Handy commands: pm2 status, pm2 logs myapp and pm2 restart myapp.

3. Put nginx in front

Install nginx with sudo apt install nginx, then create /etc/nginx/sites-available/myapp:

server {
    listen 80;
    server_name YOUR_DOMAIN;

    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
    }
}

Replace YOUR_DOMAIN with your real domain. The last two header lines let WebSockets work, which matters if your app uses them.

Enable it and test the config before reloading, so a typo can't take nginx down:

sudo ln -s /etc/nginx/sites-available/myapp /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx

4. Add HTTPS

Follow Set up an SSL certificate (HTTPS). It adds the certificate and the redirect to your nginx config for you.

5. Lock down the ports

With nginx handling traffic, only ports 80 and 443 (plus SSH) need to be open. See Set up a firewall on your VPS with UFW.

When something's off

  • 502 Bad Gateway: nginx can't reach your app. Run pm2 status and check it's online, and that the port in proxy_pass matches the one the app uses.
  • The app works by IP but not by domain: the server_name doesn't match, or DNS hasn't finished updating.
  • The app dies after a few minutes: run pm2 logs myapp and read the error. See How to read your server logs for tips.

Still stuck? Open a ticket on Discord - run /ticket in any channel, pick a department, and include your Server ID or Invoice ID so we can jump straight in.