How to deploy a Node.js app on a VPS
Take a Node.js app from your laptop to a live server. This guide covers installing Node, running the app with PM2, and fronting it with Nginx and HTTPS.
Deploying a Node.js app to a VPS means it runs 24/7 on your own server, reachable at your own domain. The production pattern is simple: run the app with a process manager, put Nginx in front as a reverse proxy, and add HTTPS. Here's the full path.
Step 1: Prepare the server
On a fresh Ubuntu VPS, update and create a non-root user to run the app (never run apps as root):
sudo apt update && sudo apt upgrade -y
sudo adduser deploy && sudo usermod -aG sudo deploy
Step 2: Install Node.js
Install a current LTS release from NodeSource:
curl -fsSL https://deb.nodesource.com/setup_lts.x | sudo -E bash -
sudo apt install nodejs -y
node -v && npm -v
Step 3: Get your code onto the server
Clone from Git (recommended) or copy files up:
git clone https://github.com/you/your-app.git
cd your-app
npm ci
Set any production environment variables in a .env file or your process manager, and build if your app needs it:
npm run build
Step 4: Keep it running with PM2
If you just run node app.js, the app dies when you close SSH or the server reboots. PM2 keeps it alive, restarts it on crashes, and starts it on boot:
sudo npm install -g pm2
pm2 start app.js --name my-app
For a build with an npm start script:
pm2 start npm --name my-app -- start
Make PM2 survive reboots:
pm2 startup systemd
pm2 save
Useful PM2 commands:
pm2 status
pm2 logs my-app
pm2 restart my-app
Step 5: Reverse-proxy with Nginx
Your app listens on a local port like 3000. You don't expose that directly — Nginx sits in front on port 80/443 and forwards requests. Install Nginx and create /etc/nginx/sites-available/my-app:
server {
listen 80;
server_name example.com www.example.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-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_cache_bypass $http_upgrade;
}
}
The Upgrade/Connection headers are important — they let WebSockets work through the proxy. Enable and reload:
sudo ln -s /etc/nginx/sites-available/my-app /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx
Step 6: Add HTTPS
Point your domain's A record at the server, then:
sudo apt install certbot python3-certbot-nginx -y
sudo certbot --nginx -d example.com -d www.example.com
Your app is now live over HTTPS at your domain.
Step 7: Open the firewall
sudo ufw allow OpenSSH
sudo ufw allow 'Nginx Full'
sudo ufw enable
Note you allow Nginx's ports, not 3000 — the app port stays private on localhost.
Updating the app
Deploys become a short routine:
cd your-app
git pull
npm ci
npm run build
pm2 restart my-app
Why a VPS for Node
Serverless is fine for some workloads, but a VPS gives you a persistent process, WebSockets, background jobs and predictable cost — all things Node apps often need. On a Nxeon NVMe VPS with full root you control the Node version, the proxy and the whole environment, and npm ci flies on fast disk. Set this pattern up once and every future deploy is a three-line update.