Dev.to WebDev 🛠 Dev 👁 0 📖 4 min read

How to Deploy a Node.js Backend the Correct Way with Nginx + PM2 on Any VPS

# Deploying a Node.js Backend in Production with PM2 & Nginx Deploying a Node.js backend is easy until you do it on a real VPS server. Locally, everything works perfectly: npm start The API launches, connects

How to Deploy a Node.js Backend the Correct Way with Nginx + PM2 on Any VPS

 # Deploying a Node.js Backend in Production with PM2 & Nginx

Deploying a Node.js backend is easy until you do it on a real VPS server.

Locally, everything works perfectly:

npm start

The API launches, connects to the database, Postman returns the correct response, and everything looks good.

But once you move your project to a production server, you'll quickly face questions like:

  • What happens if the server reboots?
  • Will the application restart automatically?
  • Should users access port 5000 directly?
  • How do you configure HTTPS?
  • Where do logs go?
  • How should you restart the application after deployment?

These are common mistakes during a first production deployment.

Many beginners simply run:

node server.js

While this works for testing, it is not suitable for production.

Problems with this approach:

  • The process may stop when your SSH session ends.
  • If the app crashes, it won't restart automatically.
  • After a server reboot, your application stays offline.

Instead, a proper production setup uses:

  • PM2 — Process manager for Node.js
  • Nginx — Reverse proxy and web server

Production Architecture

Internet
   │
   ▼
Nginx (80 / 443)
   │
   ▼
Node.js App (localhost:5000)
   │
   ▼
PM2 Process Manager

Important: Users should never communicate directly with your Node.js application.

Instead of exposing:

http://your-server-ip:5000

Users should access:

https://your-domain.com

Nginx receives the request, handles HTTP/HTTPS, and forwards it to your backend.

Step 1 — Connect to Your Server

If using the root user:

ssh root@your-server-ip

Or if using Ubuntu:

ssh ubuntu@your-server-ip

Replace your-server-ip with your VPS IP address.

Step 2 — Update Your Server

Always update the package list first.

sudo apt update && sudo apt upgrade -y

Step 3 — Install Node.js

For Node.js v20:

curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
sudo apt install nodejs -y

Verify the installation:

node -v
npm -v

Step 4 — Clone Your Backend Project

Clone your repository:

git clone https://github.com/your-username/your-backend-repo.git

Move into the project:

cd your-backend-repo

Install dependencies:

npm install

If your application uses environment variables:

nano .env

Example:

PORT=5000
DATABASE_URL=your_database_url
JWT_SECRET=your_secret_key
NODE_ENV=production

Never commit your .env file.

.gitignore

.env

Step 5 — Test the Application

If you have a start script:

npm start

Otherwise:

node server.js

This verifies that:

  • Environment variables are correct
  • Database connection works
  • Dependencies are installed
  • Production configuration is valid

Stop the application afterward:

CTRL + C

Step 6 — Install PM2

Install PM2 globally:

sudo npm install pm2 -g

Start your application.

Using server.js:

pm2 start server.js --name backend-api

Or using the npm start script:

pm2 start npm --name backend-api -- start

Verify:

pm2 list

Step 7 — Enable Auto Startup

Generate the startup script:

pm2 startup

PM2 will display another command.

Run the generated command (it begins with sudo).

Finally save the process list:

pm2 save

Now your application starts automatically after server reboots.

Step 8 — Install Nginx

Install Nginx:

sudo apt install nginx -y

Start the service:

sudo systemctl start nginx

Enable startup:

sudo systemctl enable nginx

Check status:

sudo systemctl status nginx

Step 9 — Configure Nginx

Create a configuration file:

sudo nano /etc/nginx/sites-available/backend

Paste the following:

server {
    listen 80;

    server_name your-domain.com www.your-domain.com;

    location / {
        proxy_pass http://localhost:5000;
        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;
    }
}

Replace your-domain.com with your real domain.

Enable the configuration:

sudo ln -s /etc/nginx/sites-available/backend /etc/nginx/sites-enabled/

Test the configuration:

sudo nginx -t

Restart Nginx:

sudo systemctl restart nginx

Step 10 — Configure the Firewall

If you're using UFW:

Allow SSH:

sudo ufw allow OpenSSH

Allow HTTP & HTTPS:

sudo ufw allow 'Nginx Full'

Enable the firewall:

sudo ufw enable

Only these ports should be accessible:

  • SSH
  • HTTP (80)
  • HTTPS (443)

Do not expose port 5000.

Step 11 — Configure HTTPS with Certbot

Install Certbot:

sudo apt install certbot python3-certbot-nginx -y

Generate the SSL certificate:

sudo certbot --nginx -d your-domain.com -d www.your-domain.com

Certbot automatically:

  • Creates SSL certificates
  • Updates your Nginx configuration
  • Redirects HTTP → HTTPS

Useful PM2 Commands

Check running applications

pm2 list

Restart application

pm2 restart backend-api

Stop application

pm2 stop backend-api

Delete application

pm2 delete backend-api

View logs

pm2 logs

Open monitoring dashboard

pm2 monit

Save current process list

pm2 save

The most useful command during debugging:

pm2 logs

Typical Deployment Workflow

Once everything is configured, future deployments become simple.

cd your-backend-repo

git pull

npm install

pm2 restart backend-api

Why Use PM2 & Nginx?

Instead of exposing your Node.js server directly, using PM2 and Nginx provides several advantages:

  • Automatic application restarts
  • Auto-start after server reboot
  • SSL/TLS support
  • Reverse proxy functionality
  • Better logging
  • Easier scaling
  • Multiple applications or subdomains on one server
  • Improved security

Production Best Practices

Use environment variables

Good:

process.env.JWT_SECRET

Bad:

const jwtSecret = "your_secret_here";

Only expose necessary ports

Open only:

  • 22 (SSH)
  • 80 (HTTP)
  • 443 (HTTPS)

Avoid exposing your application port (5000).

Always monitor logs

pm2 logs

Restart after deployment

pm2 restart backend-api

Summary

Production deployment is much more than simply starting your Node.js application.

A production-ready backend should:

  • ✅ Automatically restart after crashes
  • ✅ Start after server reboots
  • ✅ Use HTTPS
  • ✅ Hide internal application ports
  • ✅ Generate accessible logs
  • ✅ Be easy to update and maintain

Using PM2 together with Nginx provides a reliable, secure, and scalable setup for deploying Node.js applications on any Ubuntu-based VPS, whether it's AWS EC2, DigitalOcean, Hetzner, Azure, Linode, Contabo, or similar cloud providers.

📰 Read the original article on Dev.to WebDev

Originally published by Dev.to WebDev. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.