← Back to Blog

How to Deploy a Node.js App on a VPS (PM2 + Nginx + SSL)

Published May 3, 2026  ·  10 min read  ·  Galaxy Cloud Solutions

Getting Node.js running locally is five minutes of work. Getting it running on a server so it stays up after crashes, survives reboots, serves HTTPS, and does not expose your port directly to the internet takes a bit more. This covers all of it on Ubuntu 24.04.

⚡ Need a VPS? Plans from $5/mo — use code LAUNCH2026 for 50% off

Before You Start

Step 1: Update the System

sudo apt update && sudo apt upgrade -y

Step 2: Install Node.js via NVM

Do not use the Ubuntu package manager for Node.js — the packaged version is always outdated. NVM lets you install any version without touching system packages.

curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash
source ~/.bashrc
nvm install --lts
node --version && npm --version

Step 3: Get Your App on the Server

git clone https://github.com/you/your-app.git /var/www/your-app
cd /var/www/your-app
npm install --production

No Git? Copy directly from your local machine:

scp -r ./your-app user@your-server-ip:/var/www/your-app

Write your .env file on the server manually. Do not commit secrets to Git.

Step 4: Set Up PM2

PM2 restarts your app on crashes and brings it back after server reboots. It also handles log rotation.

npm install -g pm2
pm2 start /var/www/your-app/app.js --name "my-app"
pm2 status

Configure it to start on boot:

pm2 startup
# Copy and run the exact command it outputs — it generates one specific to your system
pm2 save

Step 5: Install Nginx

sudo apt install nginx -y
sudo systemctl enable nginx
sudo nano /etc/nginx/sites-available/your-app
server {
    listen 80;
    server_name yourdomain.com www.yourdomain.com;

    location / {
        proxy_pass http://localhost: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_cache_bypass $http_upgrade;
    }
}
sudo ln -s /etc/nginx/sites-available/your-app /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx

The symlink step trips people up constantly. A plain file in sites-enabled does nothing — it must be a symlink pointing to sites-available.

Step 6: Free SSL with Let's Encrypt

sudo apt install certbot python3-certbot-nginx -y
sudo certbot --nginx -d yourdomain.com -d www.yourdomain.com

Certbot handles the certificate, rewrites the Nginx config to redirect HTTP to HTTPS, and sets up auto-renewal. Nothing else to touch.

Step 7: Firewall

sudo ufw allow OpenSSH
sudo ufw allow 'Nginx Full'
sudo ufw enable

Verify Everything

pm2 status
sudo systemctl status nginx
curl -I https://yourdomain.com

If those all look good your app is live, process-managed, and serving HTTPS. PM2 handles the rest from here.

Deploy your Node.js app on KVM VPS from $5/mo

Full root SSH access, Ubuntu 24.04, Cloudflare DDoS protection. Use code LAUNCH2026 for 50% off your first month.

Get Started