Tutorial18 min read

How to Deploy a Node.js App to Production in 2025

Complete guide to deploying Node.js apps the right way. From zero to production with PM2, Nginx, SSL, monitoring, and auto-scaling.

What You'll Learn:

  • ✓ Server setup on Vultr/DigitalOcean (with $500 free credits)
  • ✓ Deploy your Node.js app with PM2
  • ✓ Configure Nginx as reverse proxy
  • ✓ Set up free SSL with Let's Encrypt
  • ✓ Implement monitoring and logging
  • ✓ Set up CI/CD with GitHub Actions
  • ✓ Scale horizontally with load balancing

Prerequisites

Before we begin, make sure you have:

  • A Node.js app ready to deploy (Express, Next.js, Nest.js, etc.)
  • Your code in a Git repository (GitHub, GitLab, etc.)
  • A domain name (optional but recommended)
  • Basic command line knowledge

Step 1: Choose Your Hosting Provider

You have three main options. I'll show you how to deploy on all three, but I recommend starting with Vultr or DigitalOcean for simplicity.

Vultr (Recommended)

Best value, fastest deployment

  • ✓ $300 free credits
  • ✓ Cheapest pricing ($3.50/mo starts)
  • ✓ 32 global locations
  • ✓ Deploy in 60 seconds

DigitalOcean

Best docs, great for beginners

  • ✓ $200 free credits
  • ✓ Best documentation
  • ✓ Simple UI
  • ✓ Great community

Step 2: Create Your Server

On Vultr:

  1. Sign up with the $300 free credit link
  2. Click "Deploy New Instance"
  3. Choose:
    • Type: Cloud Compute (Regular Performance)
    • Location: Nearest to your users
    • OS: Ubuntu 24.04 LTS
    • Plan: $6/mo (1 vCPU, 1GB RAM) - good for starters
  4. Add your SSH key (or use password - but SSH is more secure)
  5. Click Deploy

On DigitalOcean:

  1. Sign up with the $200 free credit link
  2. Create a new Droplet
  3. Choose:
    • OS: Ubuntu 24.04 LTS
    • Plan: Basic ($6/mo - 1GB RAM)
    • Datacenter: Nearest to your users
  4. Add your SSH key
  5. Create Droplet

Pro tip: Both Vultr and DigitalOcean bill hourly. If you mess up, just destroy the server and create a new one. It'll cost you pennies.

Step 3: Initial Server Setup

SSH into your server:

ssh root@your-server-ip

Update the system and install Node.js:

# Update system packages
apt update && apt upgrade -y

# Install Node.js 20.x (LTS)
curl -fsSL https://deb.nodesource.com/setup_20.x | bash -
apt install -y nodejs

# Verify installation
node --version  # Should show v20.x.x
npm --version   # Should show 10.x.x

# Install build essentials (needed for some npm packages)
apt install -y build-essential

Step 4: Set Up a Non-Root User (Security Best Practice)

Never run your app as root. Create a dedicated user:

# Create user
adduser nodeapp

# Add to sudo group
usermod -aG sudo nodeapp

# Switch to new user
su - nodeapp

Step 5: Clone Your App

# Install git if not already installed
sudo apt install -y git

# Clone your repository
git clone https://github.com/yourusername/your-app.git
cd your-app

# Install dependencies
npm install

# Copy environment variables
cp .env.example .env
nano .env  # Edit with your production values

Step 6: Install and Configure PM2

PM2 is a production process manager for Node.js. It keeps your app running, auto-restarts on crashes, and handles zero-downtime deployments.

# Install PM2 globally
sudo npm install -g pm2

# Start your app with PM2
pm2 start app.js --name "my-app"

# Or if you're using npm scripts:
pm2 start npm --name "my-app" -- start

# Set PM2 to start on system boot
pm2 startup systemd
# Copy and run the command it gives you

# Save PM2 process list
pm2 save

Useful PM2 commands:

pm2 list           # Show all running apps
pm2 logs           # View logs
pm2 restart my-app # Restart your app
pm2 stop my-app    # Stop your app
pm2 delete my-app  # Remove from PM2
pm2 monit          # Real-time monitoring

Step 7: Install and Configure Nginx

Nginx acts as a reverse proxy, handling SSL, static files, and load balancing. It's much better than exposing Node.js directly to the internet.

# Install Nginx
sudo apt install -y nginx

# Create Nginx config for your app
sudo nano /etc/nginx/sites-available/my-app

Add this configuration:

server {
    listen 80;
    server_name yourdomain.com www.yourdomain.com;

    location / {
        proxy_pass http://localhost:3000;  # Your Node.js app port
        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;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

Enable the site:

# Create symlink to enable site
sudo ln -s /etc/nginx/sites-available/my-app /etc/nginx/sites-enabled/

# Test Nginx config
sudo nginx -t

# Reload Nginx
sudo systemctl reload nginx

Step 8: Set Up Free SSL with Let's Encrypt

Never deploy without HTTPS in 2025. Let's Encrypt is free and automatic:

# Install Certbot
sudo apt install -y certbot python3-certbot-nginx

# Get SSL certificate (Certbot will auto-configure Nginx)
sudo certbot --nginx -d yourdomain.com -d www.yourdomain.com

# Test auto-renewal
sudo certbot renew --dry-run

Done! Your app is now running on HTTPS.

Step 9: Set Up Firewall

# Enable UFW firewall
sudo ufw allow OpenSSH
sudo ufw allow 'Nginx Full'
sudo ufw enable

# Check status
sudo ufw status

Step 10: Set Up Monitoring and Logging

PM2 Monitoring:

# Install PM2 monitoring module
pm2 install pm2-logrotate

# Configure log rotation (keep logs under control)
pm2 set pm2-logrotate:max_size 10M
pm2 set pm2-logrotate:retain 7

Set Up Error Tracking (Optional but Recommended):

Use Sentry for error tracking. It's free for small projects:

npm install @sentry/node

In your app:

const Sentry = require("@sentry/node");

Sentry.init({
  dsn: "your-sentry-dsn",
  environment: "production"
});

Step 11: Set Up CI/CD with GitHub Actions

Automate deployments so you can push code and it automatically deploys.

Create .github/workflows/deploy.yml in your repo:

name: Deploy to Production

on:
  push:
    branches: [ main ]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - name: Deploy to server
        uses: appleboy/ssh-action@master
        with:
          host: ${{ secrets.SERVER_IP }}
          username: ${{ secrets.SERVER_USER }}
          key: ${{ secrets.SSH_PRIVATE_KEY }}
          script: |
            cd /home/nodeapp/your-app
            git pull origin main
            npm install
            pm2 restart my-app

Add secrets to your GitHub repo:

  1. Go to Settings → Secrets → Actions
  2. Add SERVER_IP, SERVER_USER, and SSH_PRIVATE_KEY

Now every push to main auto-deploys!

Step 12: Database Setup (If Needed)

Option 1: PostgreSQL on Same Server

# Install PostgreSQL
sudo apt install -y postgresql postgresql-contrib

# Create database and user
sudo -u postgres psql
CREATE DATABASE myapp;
CREATE USER myappuser WITH PASSWORD 'secure_password';
GRANT ALL PRIVILEGES ON DATABASE myapp TO myappuser;
\q

Option 2: Managed Database (Easier)

Both Vultr and DigitalOcean offer managed databases. They handle backups, updates, and scaling:

  • Vultr Managed Database: Starting at $15/mo
  • DigitalOcean Managed Database: Starting at $15/mo

Worth it if you don't want to manage backups manually.

Step 13: Performance Optimization

Enable Gzip in Nginx:

Edit /etc/nginx/nginx.conf:

gzip on;
gzip_vary on;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
gzip_comp_level 6;

Enable HTTP/2:

In your Nginx site config, change:

listen 443 ssl http2;

Add Caching Headers:

location ~* \.(jpg|jpeg|png|gif|ico|css|js)$ {
    expires 1y;
    add_header Cache-Control "public, immutable";
}

Step 14: Scaling Your App

Vertical Scaling (Easier):

Just upgrade your server to more CPU/RAM. On Vultr/DigitalOcean, you can resize with a few clicks. Downtime is usually just a minute or two.

Horizontal Scaling (Better):

Run multiple instances with PM2 cluster mode:

# Run app on all CPU cores
pm2 start app.js -i max

# Or specify number of instances
pm2 start app.js -i 4

Common Issues and Solutions

App crashes immediately:

  • Check PM2 logs: pm2 logs
  • Verify environment variables are set correctly
  • Make sure port 3000 (or your app port) isn't already in use

502 Bad Gateway error:

  • App probably isn't running - check pm2 list
  • Verify the proxy_pass port in Nginx matches your app
  • Check Nginx logs: sudo tail -f /var/log/nginx/error.log

SSL certificate won't install:

  • Make sure your domain DNS is pointing to server IP
  • Wait 5-10 minutes after changing DNS
  • Verify ports 80 and 443 are open in firewall

Estimated Costs

ConfigurationVultrDigitalOceanGood For
1GB RAM, 1 vCPU$6/mo$6/moSmall apps, side projects
2GB RAM, 1 vCPU$12/mo$12/moProduction apps, light traffic
4GB RAM, 2 vCPU$24/mo$24/moHeavy traffic, APIs
+ Managed Database+$15/mo+$15/moRecommended for production

But remember: With Vultr's $300 free credit or DigitalOcean's $200 free credit, you can run for months without paying anything.

Conclusion

You now have a production-ready Node.js deployment with:

  • ✓ Zero-downtime deployments with PM2
  • ✓ HTTPS with free SSL certificates
  • ✓ Nginx reverse proxy for performance
  • ✓ Automated CI/CD deployments
  • ✓ Monitoring and error tracking
  • ✓ Ready to scale

Total setup time: 30-45 minutes for your first deployment. Future deploys take seconds with CI/CD.

Cost: $0 for the first few months if you use the free credits, then $6-24/month depending on traffic.

Ready to Deploy Your App?

Both links give you free credits • Deploy in under 30 minutes • Cancel anytime