Reverse proxy cheatsheet.

Basic proxy

server {
    listen 80;
    server_name api.example.com;
    
    location / {
        proxy_pass http://127.0.0.1:8000;
        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;
    }
}

Upstream block (load balancer)

upstream app {
    server 10.0.0.1:8000;
    server 10.0.0.2:8000;
    server 10.0.0.3:8000 backup;
    keepalive 32;
}

server {
    location / {
        proxy_pass http://app;
        proxy_http_version 1.1;
        proxy_set_header Connection "";        # required for keepalive
    }
}

Load balancing methods

upstream app {
    # default: round-robin
    
    least_conn;                       # least connections
    
    ip_hash;                          # sticky by IP
    
    hash $request_uri consistent;     # consistent hashing
    
    server 10.0.0.1 weight=3;
    server 10.0.0.2 weight=1;
    server 10.0.0.3 max_fails=3 fail_timeout=30s;
}

Path rewrite in proxy_pass

# /api/users → /users on backend
location /api/ {
    proxy_pass http://app/;            # trailing / strips /api/
}

# /api/users → /api/users on backend
location /api/ {
    proxy_pass http://app;             # no trailing /
}

The trailing slash matters.

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;
proxy_set_header X-Request-ID $request_id;

App must trust X-Forwarded-* only from known proxies.

WebSocket / HTTP upgrade

location /ws/ {
    proxy_pass http://app;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_read_timeout 86400;
    proxy_send_timeout 86400;
}

map $http_upgrade $connection_upgrade {
    default upgrade;
    '' close;
}

Buffering

proxy_buffering on;                    # default
proxy_buffer_size 16k;
proxy_buffers 8 16k;
proxy_busy_buffers_size 32k;

# Disable for SSE / streaming:
location /stream {
    proxy_buffering off;
    proxy_cache off;
    proxy_set_header Connection '';
    proxy_http_version 1.1;
    chunked_transfer_encoding off;
    proxy_pass http://app;
}

Timeouts

proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;

client_body_timeout 60s;
client_header_timeout 60s;
send_timeout 60s;

keepalive_timeout 65;

For long-running APIs / uploads, increase appropriately.

Body size

client_max_body_size 10m;

Default 1m. Increase for uploads.

proxy_redirect

proxy_redirect http://backend/ https://example.com/;

Rewrite redirect responses from backend.

Health checks

Open source nginx: passive only (max_fails, fail_timeout).

Nginx Plus: active health checks.

Alternative: nginx-haproxy combo, or use Kubernetes services.

Retries

proxy_next_upstream error timeout http_502 http_503 http_504;
proxy_next_upstream_tries 3;
proxy_next_upstream_timeout 30s;

Retry on errors against next upstream.

map $http_x_canary $upstream {
    default app-stable;
    "1"     app-canary;
}

upstream app-stable { server 10.0.0.1; }
upstream app-canary { server 10.0.0.2; }

server {
    location / {
        proxy_pass http://$upstream;
    }
}

Connection pooling

upstream app {
    server 10.0.0.1:8000;
    keepalive 64;
    keepalive_requests 1000;
    keepalive_timeout 60s;
}

server {
    location / {
        proxy_pass http://app;
        proxy_http_version 1.1;
        proxy_set_header Connection "";
    }
}

SSL to backend

upstream app {
    server backend.example.com:443;
}

location / {
    proxy_pass https://app;
    proxy_ssl_server_name on;
    proxy_ssl_verify off;          # or on with proper certs
    proxy_ssl_session_reuse on;
}

gRPC

server {
    listen 50051 http2;
    
    location / {
        grpc_pass grpc://backend:50052;
        grpc_set_header X-Real-IP $remote_addr;
    }
}

Common mistakes

  • Forgetting proxy_http_version 1.1 → no keepalive, no websocket.
  • Missing trailing / in proxy_pass → wrong path.
  • Trusting X-Forwarded-For from untrusted source.
  • proxy_buffering on for SSE → response delayed.
  • Host header lost without proxy_set_header Host.

Read this next

If you want my reverse-proxy templates, they’re at rajpoot.dev .


Building something AI-, backend-, or data-heavy and want a second pair of eyes? I do consulting and freelance work — see my projects and ways to reach me at rajpoot.dev .