NGINX — Reverse Proxy, Load Balancing & SSL

30 minLesson 14 of 16

Learning Objectives

  • Configure NGINX as a reverse proxy
  • Set up load balancing across multiple backends
  • Understand load balancing algorithms (round-robin, least_conn, ip_hash)
  • Secure sites with Let's Encrypt SSL/TLS certificates

Reverse Proxy

A reverse proxy sits between clients and backend servers, forwarding requests:

Client → NGINX (Reverse Proxy) → Backend Server(s)

Why Use a Reverse Proxy?

  • Security — Hide backend server details
  • SSL termination — Handle encryption at one point
  • Caching — Reduce backend load
  • Load balancing — Distribute traffic

Basic Reverse Proxy Config

server {
    listen 80;
    server_name app.nextgenplayground.org;
 
    location / {
        proxy_pass http://localhost:3000;
        proxy_http_version 1.1;
        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;
    }
}

Important Proxy Headers

HeaderPurpose
HostOriginal hostname requested
X-Real-IPClient's actual IP address
X-Forwarded-ForChain of proxy IPs
X-Forwarded-ProtoOriginal protocol (http/https)

Load Balancing

Distribute traffic across multiple backend servers:

Round Robin (Default)

upstream backend {
    server 192.168.1.10:3000;
    server 192.168.1.11:3000;
    server 192.168.1.12:3000;
}
 
server {
    listen 80;
    server_name app.nextgenplayground.org;
 
    location / {
        proxy_pass http://backend;
    }
}

Least Connections

Send to the server with fewest active connections:

upstream backend {
    least_conn;
    server 192.168.1.10:3000;
    server 192.168.1.11:3000;
    server 192.168.1.12:3000;
}

IP Hash (Session Persistence)

Same client always goes to same server:

upstream backend {
    ip_hash;
    server 192.168.1.10:3000;
    server 192.168.1.11:3000;
    server 192.168.1.12:3000;
}

Weighted Load Balancing

Send more traffic to powerful servers:

upstream backend {
    server 192.168.1.10:3000 weight=5;  # Gets 5x traffic
    server 192.168.1.11:3000 weight=3;  # Gets 3x traffic
    server 192.168.1.12:3000 weight=1;  # Gets 1x traffic
}

Health Checks

NGINX automatically detects failed backends:

upstream backend {
    server 192.168.1.10:3000 max_fails=3 fail_timeout=30s;
    server 192.168.1.11:3000 max_fails=3 fail_timeout=30s;
    server 192.168.1.12:3000 backup;  # Only used if others fail
}

SSL/TLS with Let's Encrypt

Install Certbot

sudo apt install certbot python3-certbot-nginx -y

Obtain Certificate

sudo certbot --nginx -d app.nextgenplayground.org

Certbot will:

  1. Verify domain ownership
  2. Generate SSL certificate
  3. Automatically update NGINX config
  4. Set up auto-renewal

Resulting Config

Certbot modifies your server block:

server {
    listen 443 ssl;
    server_name app.nextgenplayground.org;
 
    ssl_certificate /etc/letsencrypt/live/app.nextgenplayground.org/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/app.nextgenplayground.org/privkey.pem;
    ssl_protocols TLSv1.2 TLSv1.3;
 
    location / {
        proxy_pass http://backend;
    }
}
 
# HTTP to HTTPS redirect
server {
    listen 80;
    server_name app.nextgenplayground.org;
    return 301 https://$host$request_uri;
}

Auto-Renewal

Certbot sets up automatic renewal. Verify:

# Test renewal
sudo certbot renew --dry-run
 
# Check timer
sudo systemctl status certbot.timer

Complete Production Example

upstream app_servers {
    least_conn;
    server 10.0.1.10:8080 weight=3;
    server 10.0.1.11:8080 weight=2;
    server 10.0.1.12:8080 backup;
}
 
server {
    listen 80;
    server_name app.nextgenplayground.org;
    return 301 https://$host$request_uri;
}
 
server {
    listen 443 ssl http2;
    server_name app.nextgenplayground.org;
 
    ssl_certificate /etc/letsencrypt/live/app.nextgenplayground.org/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/app.nextgenplayground.org/privkey.pem;
 
    # Security headers
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-Content-Type-Options "nosniff" always;
 
    # Proxy to backend
    location / {
        proxy_pass http://app_servers;
        proxy_http_version 1.1;
        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_cache_bypass $http_upgrade;
    }
 
    # Static assets (served directly by NGINX)
    location /static/ {
        alias /var/www/app/static/;
        expires 30d;
        access_log off;
    }
 
    # Health check endpoint
    location /health {
        access_log off;
        return 200 "OK\n";
    }
}

Summary

  • Reverse proxy forwards client requests to backend servers
  • Always set proxy_set_header for proper IP forwarding
  • Load balancing algorithms: round-robin, least_conn, ip_hash, weighted
  • max_fails and fail_timeout handle backend failures
  • Let's Encrypt provides free SSL certificates via Certbot
  • Always redirect HTTP → HTTPS in production
  • Use backup servers for failover

Next Steps

Next, we'll set up MariaDB — the database server used in production LEMP stacks.