VPS

How to deploy a Node.js app on a VPS with Nginx, PM2 and HTTPS

Publish your Node.js app on an Ubuntu 24.04 VPS so it always runs on your domain over HTTPS. We install Node LTS, keep the app alive with PM2, set up Nginx as a reverse proxy, enable Let’s Encrypt and fix the most common errors along the way.

HostSpace team

9 min read

Article cover: How to deploy a Node.js app on a VPS with Nginx, PM2 and HTTPS

Key takeaways

  • Your app listens on a local port and Nginx receives visitors on 80 and 443.
  • PM2 keeps it alive and restarts it after a reboot with pm2 startup and pm2 save.
  • Certbot with the Nginx plugin enables free HTTPS and renews it automatically.
  • A 502 Bad Gateway almost always means the app isn’t running.
Article contents

Before you start

In this guide you'll publish a Node.js app on an Ubuntu 24.04 VPS so that it keeps running after you close the terminal, restarts itself if it crashes and answers on your domain over HTTPS. The setup is the most common one in production: your app listens on a local port, PM2 keeps it alive and Nginx receives visitors and hands requests over to it.

You need the following ready:

  • An Ubuntu 24.04 VPS with SSH access as a user with sudo (not root). If you still sign in with a password, first set up your SSH keys.
  • A domain whose A record points to the server's IP, for example 203.0.113.10. We cover it in point your domain to your hosting.
  • Your app in a Git repository (GitHub, GitLab or similar).

Step 1: install Node.js LTS

Ubuntu's repositories usually ship an outdated Node version. Always use an LTS (long-term support) release, which is the one recommended for production. You have two options:

  1. NodeSource, which installs Node system-wide with apt and updates it along with your other packages:
Terminal
curl -fsSL https://deb.nodesource.com/setup_lts.x | sudo -E bash -
sudo apt install -y nodejs git
node -v
npm -v
  1. nvm, if you need several Node versions on the same server. Install nvm from its official repository and then run nvm install --lts. Keep in mind that with nvm, Node is installed for your user only.

Step 2: clone and install your app

Create a folder for your apps, clone the repository and install dependencies. Use npm ci instead of npm install: it installs exactly what your package-lock.json says and fails if anything doesn't match.

Terminal
sudo mkdir -p /var/www && sudo chown $USER:$USER /var/www
cd /var/www
git clone https://github.com/your-user/yourbusiness.git
cd yourbusiness
npm ci
npm run build

Skip npm run build if your project has no build step. Before moving on, check that the app starts with npm start and answers on http://127.0.0.1:3000 using curl from another terminal.

Environment variables

Database passwords, API keys and other secrets don't belong in the repository. A simple approach is to declare them in a PM2 config file that only exists on the server:

ecosystem.config.cjs
module.exports = {
  apps: [
    {
      name: 'yourbusiness',
      script: 'dist/server.js',
      cwd: '/var/www/yourbusiness',
      env: {
        NODE_ENV: 'production',
        PORT: 3000,
        DATABASE_URL: 'postgres://user:password@127.0.0.1:5432/yourbusiness',
      },
    },
  ],
};

Add ecosystem.config.cjs to your .gitignore and protect it with chmod 600. Change script to your app's entry file.

Step 3: keep the app alive with PM2

If you run node server.js directly, the app stops as soon as you close the SSH session. PM2 is a process manager that runs it in the background, restarts it if it crashes and brings it back up when the server reboots.

  1. Install PM2 globally with sudo npm install -g pm2.
  2. Start the app with pm2 start ecosystem.config.cjs and check that it shows as online with pm2 status.
  3. Run pm2 startup. PM2 prints a command starting with sudo env PATH=...: copy it and run it as-is to register PM2 as a system service.
  4. Save the process list with pm2 save, so PM2 knows which apps to start when the server boots.

To follow the logs live, use pm2 logs yourbusiness; they'll help you diagnose almost any problem.

Step 4: set up Nginx as a reverse proxy

Nginx listens on ports 80 and 443, handles HTTPS and forwards each request to your app on port 3000. Install it with sudo apt install -y nginx and create a file for your site:

/etc/nginx/sites-available/yourbusiness.com
server {
    listen 80;
    listen [::]:80;
    server_name yourbusiness.com www.yourbusiness.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_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_cache_bypass $http_upgrade;
    }
}

The Upgrade and Connection headers make WebSockets work (for example, Socket.IO). The X-Forwarded headers tell your app the visitor's real IP and whether they arrived over HTTPS. Now enable the site:

Terminal
sudo ln -s /etc/nginx/sites-available/yourbusiness.com /etc/nginx/sites-enabled/
sudo rm /etc/nginx/sites-enabled/default
sudo nginx -t
sudo systemctl reload nginx

nginx -t validates the syntax before applying changes. If it reports an error, don't reload until you fix it.

Step 5: firewall and HTTPS with Let's Encrypt

  1. Allow web traffic through the firewall with sudo ufw allow 'Nginx Full', which opens ports 80 and 443. Check with sudo ufw status that SSH (OpenSSH) is still allowed before enabling UFW.
  2. Install Certbot with its Nginx plugin: sudo apt install -y certbot python3-certbot-nginx.
  3. Request the free certificate. Certbot edits your Nginx config and adds the HTTP to HTTPS redirect:
Terminal
sudo certbot --nginx -d yourbusiness.com -d www.yourbusiness.com
sudo certbot renew --dry-run

The second command simulates a renewal: Let's Encrypt certificates are short-lived and Certbot renews them automatically with a scheduled task. To understand what the certificate protects, read what an SSL certificate is.

Step 6: deploy updates

Every time you push changes to your repository, deploy them on the server like this:

Terminal
cd /var/www/yourbusiness
git pull
npm ci
npm run build
pm2 reload yourbusiness

pm2 reload restarts the app with as little downtime as possible. If you changed variables in ecosystem.config.cjs, use pm2 reload ecosystem.config.cjs --update-env so they take effect. Once you repeat these steps often, save them in a deploy.sh script.

Common errors and how to fix them

502 Bad Gateway

Nginx is working but can't reach your app. Check with pm2 status that it is online, look at pm2 logs in case it crashes on startup and confirm the proxy_pass port matches the one your app listens on. Test with curl http://127.0.0.1:3000 from the server.

Error: listen EADDRINUSE

Another process is already using that port, usually an old copy of your app you started by hand or a duplicate PM2 process. Find it with sudo ss -ltnp | grep 3000, stop it and let only PM2 manage the app.

The app stops after logging out or rebooting

It was started with node or npm start in the terminal, or PM2 isn't registered as a service. Start it with PM2 and repeat pm2 startup and pm2 save. If you use nvm, run pm2 startup again every time you switch Node versions.

Certbot can't validate the domain

The A record doesn't point to your server yet or port 80 is closed. Check the IP with dig +short yourbusiness.com, wait for DNS to propagate and review UFW.

Frequently asked questions

Nginx handles HTTPS, multiple domains on one server, static files and slow connections more efficiently, and lets you keep your app on a local port instead of exposing it directly.

Start it with a process manager such as PM2 and run pm2 startup and pm2 save so it also comes back when the server reboots.

Nginx can’t connect to your app: it is stopped, crashes on startup or listens on a different port than proxy_pass. Check pm2 status and pm2 logs.

Both work. NodeSource installs Node system-wide and updates it with apt; nvm makes sense if you need several versions on the same server.

HostSpace team

We provide hosting, domains, VPS servers and business email with Spanish-speaking support. We write about what our customers ask us most.