←Back to Guides
โšกWeb Architectureโฑ๏ธ 14 min read•Updated October 2026

Nginx Reverse Proxy Master Cheat Sheet: SSL, Upstreams, Buffers & Rate Limiting

The battle-tested production manual for Nginx reverse proxy architecture: zero-latency proxy_pass buffering, WebSocket upgrades, upstream load balancing with health checks, SSL/TLS TLSv1.3 hardening, and rate-limiting zones.

#Nginx#DevOps#Reverse Proxy#Web Server#SSL/TLS#Performance#Sysadmin

1. Production Reverse Proxy Block & Header Forwarding

A standard proxy_pass drops the original client IP address and host headers unless explicitly forwarded. This configuration preserves client identity, protocols, and real IP addresses across upstream microservices.
The Canonical proxy_pass Block with Real IP Preservation
server {
    listen 80;
    listen [::]:80;
    server_name api.mayonlinetools.com;

    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_http_version 1.1;

        # Mandatory Client Identity Headers
        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_set_header X-Forwarded-Host $host;
        proxy_set_header X-Forwarded-Port $server_port;

        # Timeouts and Connection Hygiene
        proxy_connect_timeout 60s;
        proxy_send_timeout 60s;
        proxy_read_timeout 60s;
    }
}

Prevents backend node apps from assuming all traffic originates from 127.0.0.1.

Proxy Buffer & Upload Size Tuning
location /api/ {
    proxy_pass http://127.0.0.1:3000;
    proxy_buffering on;
    proxy_buffer_size 16k;        # buffer for the response headers
    proxy_buffers 8 16k;          # buffers for the response body, per connection
    proxy_busy_buffers_size 32k;  # data that may be sent to the client while still reading
    client_max_body_size 20m;     # larger uploads are rejected with 413
}

Tune buffers for large API responses and cap request bodies; disable buffering only for streaming endpoints (see SSE below).

2. WebSocket Upgrades & Server-Sent Events (SSE)

Nginx closes idle HTTP/1.0 connections and buffers streaming chunks by default, breaking WebSockets and real-time Server-Sent Events (SSE). Use connection mapping and buffer bypasses.
Universal WebSocket & HTTP Connection Mapping (nginx.conf)
# In http context:
map $http_upgrade $connection_upgrade {
    default upgrade;
    ''      close;
}

# In server location context:
location /ws/ {
    proxy_pass http://127.0.0.1:8080;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection $connection_upgrade;
    proxy_read_timeout 86400s; # Keep persistent socket alive for 24 hours
}

Dynamically upgrades protocol only when the client sends an Upgrade header.

SSE Streaming Endpoint (Zero Buffering)
location /events/ {
    proxy_pass http://127.0.0.1:3000;
    proxy_http_version 1.1;
    proxy_set_header Connection '';
    proxy_buffering off;
    proxy_cache off;
    chunked_transfer_encoding off;
}

Disables proxy buffering so chunks stream to the browser with zero latency.

3. Upstream Load Balancing & Failover Policies

Distribute client traffic across multiple application servers with automatic failure detection and traffic weighting.
Declarative Upstream Cluster with Least Connections & Passive Health Checks
upstream nodejs_backend {
    least_conn;
    server 10.0.0.11:3000 weight=3 max_fails=3 fail_timeout=30s;
    server 10.0.0.12:3000 weight=2 max_fails=3 fail_timeout=30s;
    server 10.0.0.13:3000 backup; # Only receives traffic if primary nodes fail
    keepalive 32;
}

server {
    location / {
        proxy_pass http://nodejs_backend;
        # Required for upstream keepalive to take effect:
        proxy_http_version 1.1;
        proxy_set_header Connection "";
        proxy_next_upstream error timeout http_502 http_503;
    }
}

least_conn routes requests to the server with the lowest active connections; keepalive pools persistent sockets. max_fails/fail_timeout are passive health checks; active health_check is an NGINX Plus feature.

Balancing StrategyDirectiveBest Fit ForCopy
Round Robin (Default)No directive neededIdentical servers with short homogeneous requests
Least Connectionsleast_conn;Varying request durations (file uploads, heavy queries)
IP Hash (Sticky)ip_hash;Stateful sessions requiring sticky client affinity
Generic Hashhash $request_uri consistent;Consistent cache sharding across backend nodes

4. SSL/TLS TLSv1.3 Hardening & HSTS

Secure your web server to achieve an A+ rating on Qualys SSL Labs: disable legacy TLS protocols (SSLv3, TLSv1.0, TLSv1.1), configure modern ECDHE ciphers, and set HSTS. Enable OCSP stapling only if your CA still publishes OCSP responders (Let's Encrypt ended OCSP support in 2025).
Hardened TLSv1.3 / TLSv1.2 Security Drop-In
listen 443 ssl;
http2 on; # nginx >= 1.25.1 (older versions: listen 443 ssl http2;)
ssl_certificate /etc/letsencrypt/live/mayonlinetools.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/mayonlinetools.com/privkey.pem;

# Modern Protocols & Ciphers
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;

# Session Caching
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
ssl_session_tickets off;

# OCSP Stapling: only enable if your CA certificate still contains an OCSP responder URL.
# Let's Encrypt ended OCSP support in 2025, so nginx would log "ssl_stapling ignored" for its certificates.
# ssl_stapling on;
# ssl_stapling_verify on;
# resolver 1.1.1.1 8.8.8.8 valid=300s;

# HTTP Strict Transport Security (HSTS)
# Start with a short max-age (e.g. 300) and omit preload until every subdomain serves HTTPS: preload is very hard to undo.
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;

Locks out insecure legacy ciphers and enforces encrypted transport browser-wide.

HTTP to HTTPS Redirect
server {
    listen 80;
    listen [::]:80;
    server_name api.mayonlinetools.com;
    return 301 https://$host$request_uri;
}

Send every plain-HTTP request to HTTPS with a permanent redirect before HSTS takes over in the browser.

5. Rate Limiting Zones & DoS Defense

Protect APIs and authentication endpoints from brute-force dictionary attacks and web scrapers using leaky bucket rate limiting.
Leaky Bucket Rate Limiting (Login & API Protection)
# In http context: Allocate 10MB zone tracking IP addresses (160k IPs)
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s;
limit_conn_zone $binary_remote_addr zone=conn_limit:10m;

server {
    location /api/ {
        # Enforce 10 req/s with burst queue of 20 requests without delay
        limit_req zone=api_limit burst=20 nodelay;
        limit_conn conn_limit 10; # Max 10 simultaneous connections per IP
        limit_req_status 429;
        limit_conn_status 429;

        proxy_pass http://127.0.0.1:3000;
    }
}

Smooths traffic spikes while returning HTTP 429 Too Many Requests to aggressive scrapers.

Safely Test Syntax and Reload Without Downtime
nginx -t && systemctl reload nginx

nginx -t guarantees configuration validity before applying changes live.

๐ŸŽ“Explore More Guides

โœจ