Reverse Proxy with Nginx and Apache2 for Node.js

Khimananda Oli 5 min read DevOps
Reverse Proxy with Nginx and Apache2 for Node.js

Reverse Proxy with Nginx and Apache2 for Node.js

SSL, WebSocket support, security headers, and a side-by-side config comparison — Part 3 of 3

In Part 1 we set up NVM and PM2. In Part 2 we started the app. Now let's expose it to the internet with a proper reverse proxy.

Architecture

Internet → [Port 80/443] → Nginx or Apache2 → [localhost:3000] → PM2 → Node.js App

The reverse proxy handles: SSL termination, security headers, static file serving, WebSocket upgrades.

Prerequisites

  • Node.js app running on localhost:3000 via PM2 (Part 2)
  • A domain name pointing to your server
  • Nginx or Apache2 installed

Option A: Nginx Configuration

Install Nginx

sudo apt update
sudo apt install nginx -y
sudo systemctl enable nginx
sudo systemctl start nginx

Create a Server Block

# /etc/nginx/sites-available/myapp

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

    # Redirect HTTP to HTTPS
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl http2;
    server_name yourdomain.com www.yourdomain.com;

    ssl_certificate /etc/letsencrypt/live/yourdomain.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/yourdomain.com/privkey.pem;
    ssl_session_timeout 1d;
    ssl_session_cache shared:SSL:50m;
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
    ssl_prefer_server_ciphers off;

    # Security headers
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-XSS-Protection "1; mode=block" always;
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;

    # Static files (optional — serve directly from Nginx)
    location /public {
        alias /opt/myapp/public;
        expires 30d;
        add_header Cache-Control "public, immutable";
    }

    # Proxy to Node.js
    location / {
        proxy_pass http://localhost:3000;
        proxy_http_version 1.1;

        # WebSocket support
        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_set_header X-Forwarded-Proto $scheme;

        proxy_connect_timeout 60s;
        proxy_send_timeout 60s;
        proxy_read_timeout 60s;
        proxy_buffering off;
    }

    access_log /var/log/nginx/myapp.access.log;
    error_log /var/log/nginx/myapp.error.log;
}

Enable and Test

sudo ln -s /etc/nginx/sites-available/myapp /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx

Option B: Apache2 Configuration

Install Apache2 and Enable Modules

sudo apt update
sudo apt install apache2 -y
sudo a2enmod proxy proxy_http proxy_wstunnel ssl headers rewrite
sudo systemctl restart apache2

Create a Virtual Host

# /etc/apache2/sites-available/myapp.conf


    ServerName yourdomain.com
    ServerAlias www.yourdomain.com
    Redirect permanent / https://yourdomain.com/



    ServerName yourdomain.com
    ServerAlias www.yourdomain.com

    SSLEngine on
    SSLCertificateFile /etc/letsencrypt/live/yourdomain.com/fullchain.pem
    SSLCertificateKeyFile /etc/letsencrypt/live/yourdomain.com/privkey.pem

    # Security headers
    Header always set X-Frame-Options "SAMEORIGIN"
    Header always set X-Content-Type-Options "nosniff"
    Header always set X-XSS-Protection "1; mode=block"
    Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"

    # Static files
    Alias /public /opt/myapp/public
    
        Options -Indexes
        AllowOverride None
        Require all granted
        ExpiresActive On
        ExpiresDefault "access plus 30 days"
    

    # Proxy to Node.js
    ProxyPreserveHost On
    ProxyRequests Off

    # WebSocket support
    RewriteEngine On
    RewriteCond %{HTTP:Upgrade} websocket [NC]
    RewriteCond %{HTTP:Connection} upgrade [NC]
    RewriteRule ^/?(.*) "ws://localhost:3000/$1" [P,L]

    ProxyPass / http://localhost:3000/
    ProxyPassReverse / http://localhost:3000/

    RequestHeader set X-Forwarded-Proto "https"
    RequestHeader set X-Real-IP "%{REMOTE_ADDR}s"

    ErrorLog /var/log/apache2/myapp.error.log
    CustomLog /var/log/apache2/myapp.access.log combined

Enable and Test

sudo a2ensite myapp.conf
sudo apache2ctl configtest
sudo systemctl reload apache2

SSL with Let's Encrypt (Both Servers)

# Install Certbot
sudo apt install certbot -y

# For Nginx
sudo apt install python3-certbot-nginx -y
sudo certbot --nginx -d yourdomain.com -d www.yourdomain.com

# For Apache2
sudo apt install python3-certbot-apache -y
sudo certbot --apache -d yourdomain.com -d www.yourdomain.com

# Auto-renewal test
sudo certbot renew --dry-run

Nginx vs Apache2 — Quick Comparison

FeatureNginxApache2
PerformanceExcellent (event-driven)Good (process/thread)
ConfigurationDeclarative blocksDirective-based
WebSocketproxy_set_header Upgrademod_proxy_wstunnel + RewriteRule
Static filesFaster, built-inGood with mod_expires
.htaccessNot supportedFull support
MemoryLowerHigher
Let's Encryptcertbot --nginxcertbot --apache

Recommendation: Use Nginx for new setups. Use Apache2 if you're already running it or need .htaccess support.

Verify the Full Stack

# Check all services
pm2 status
sudo systemctl status nginx   # or apache2

# Test SSL
curl -I https://yourdomain.com

# Check headers
curl -I https://yourdomain.com | grep -E "X-Frame|Strict|X-Content"

# Logs
sudo tail -f /var/log/nginx/myapp.error.log
pm2 logs myapp

Full Stack Summary

  • ✅ Part 1: NVM + Node.js version management + PM2 setup + log rotation
  • ✅ Part 2: App running with PM2, cluster mode, zero-downtime reloads
  • ✅ Part 3: Nginx/Apache2 reverse proxy + SSL + security headers + WebSocket

Your Node.js app is now running in production — managed by PM2, protected by SSL, served through a proper reverse proxy.

Check out more DevOps guides on khimananda.com.