How to Deploy a Node.js App on a VPS (PM2 + Nginx + SSL)
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% offBefore You Start
- Ubuntu 24.04 VPS with root or sudo SSH access (Nebula 1 works for most apps)
- A domain name with an A record pointing at your server IP
- Your app code in Git or ready to copy over
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