How to Deploy a Node.js App on a VPS

Deploying a Node.js application on a VPS gives you full control over your environment. This guide walks you through setting up Node.js, configuring PM2, and putting Nginx in front of your app — step by step.
Running a Node.js app on your local machine is one thing — getting it live on a server and keeping it running reliably is another. A VPS (Virtual Private Server) gives you a real Linux environment with root access, so you control every part of the stack. This guide covers how to deploy a Node.js app on a VPS from scratch: installing Node, managing your process with PM2, putting Nginx in front as a reverse proxy, and securing everything with free HTTPS.
What You'll Need Before You Start
This guide assumes you have:
A VPS running Ubuntu 22.04 or 24.04 LTS
SSH access to the server (root or a sudo user)
A domain name pointed at your VPS IP address
Your Node.js project code ready to deploy (on GitHub or transferred via SCP/SFTP)
If you're not sure which VPS size to pick, a 2 vCPU / 2 GB RAM instance is a comfortable starting point for most Node.js applications.

Step 1: Install Node.js
Ubuntu's default package repositories often ship a significantly outdated version of Node.js. Install from the official NodeSource repository instead to get a current LTS release:
curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash -
sudo apt install -y nodejsVerify the installation:
node -v
npm -vIf your project needs a specific Node version, consider nvm (Node Version Manager) instead — it lets you switch between versions and pin one per project using a .nvmrc file.
Step 2: Upload Your App and Install Dependencies
Get your code onto the server. If it's on GitHub, clone it directly:
git clone https://github.com/your-username/your-app.git
cd your-app
npm install --omit=devThe --omit=dev flag skips development dependencies. If your app has a build step, run it now:
npm run buildEnvironment variables
Never hardcode secrets in source files. Create a .env file on the server (not committed to Git) with the values your app needs:
PORT=3000
NODE_ENV=production
DATABASE_URL=your-connection-stringYour app should read these via process.env. The dotenv library handles this automatically on startup if you call require('dotenv').config() at the top of your entry file.
Step 3: Keep Your App Running with PM2
Node.js apps exit when they crash or when your SSH session closes. PM2 is a process manager that keeps your app alive, restarts it on failure, and survives server reboots.
Install PM2 globally:
sudo npm install -g pm2Start your app:
pm2 start app.js --name "my-app"Or if you use an npm start script:
pm2 start npm --name "my-app" -- startConfigure PM2 to start automatically after a reboot:
pm2 startup
pm2 saveThe pm2 startup command prints a line you need to run as root — copy and paste it exactly. After that, pm2 save freezes the current process list so it's restored on every boot.
Useful PM2 commands
pm2 status— see all running processes and their uptimepm2 logs my-app— tail application logs in real timepm2 reload my-app— zero-downtime restart (replaces workers one by one)pm2 restart my-app— full restart with a brief gap in availability
Step 4: Set Up Nginx as a Reverse Proxy
Your Node app listens on an internal port like 3000. Don't expose that port directly to the internet — put Nginx in front to handle public HTTP/HTTPS traffic and forward it to Node.
Install Nginx:
sudo apt install -y nginxCreate a site configuration file:
sudo nano /etc/nginx/sites-available/my-appAdd the following, replacing yourdomain.com with your actual domain:
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_cache_bypass $http_upgrade;
}
}Enable the site and reload Nginx:
sudo ln -s /etc/nginx/sites-available/my-app /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginxAlways run nginx -t first — it validates your config and reports syntax errors before anything is reloaded.
Step 5: Add HTTPS with Let's Encrypt
Free SSL certificates from Let's Encrypt, handled automatically by Certbot:
sudo apt install -y certbot python3-certbot-nginx
sudo certbot --nginx -d yourdomain.com -d www.yourdomain.comCertbot updates your Nginx config to handle HTTPS and redirect HTTP traffic automatically. Certificates renew every 90 days via a system timer — no manual action needed.
Pushing Updates
Once running, deploying changes is a short sequence of commands:
SSH into the server
cd your-app && git pullnpm install --omit=dev(if dependencies changed)npm run build(if there's a build step)pm2 reload my-app— reloads without dropping active connections
For teams or frequent deploys, this is easy to automate with a shell script or a GitHub Actions workflow that SSHs into the VPS and runs these steps on every push to main.
Deploying a Node.js app on a VPS comes down to a few well-understood pieces: Node runs the application, PM2 keeps it alive, and Nginx handles public traffic and SSL termination. Once this stack is in place, it's reliable, low-maintenance, and gives you complete control over your hosting environment.