Nginx as a Reverse Proxy
A reverse proxy sits in front of your application servers and handles client connections on their behalf. Nginx is the most widely used one: it terminates TLS, serves static files efficiently, buffers slow clients so application workers aren't tied up, routes requests to the right backend by host and path, and adds load balancing, caching, and rate limiting.
Most Nginx configurations for web apps are variations on a small core: server blocks, location matching, proxy_pass, and the right forwarded headers. The details (trailing slashes, timeouts, buffering, WebSocket upgrades) are where subtle bugs live.
TL;DR
- A
serverblock matches requests bylistenport andserver_name;locationblocks match URI paths. proxy_pass http://upstream;forwards requests. A trailing slash or URI inproxy_passchanges how the path is rewritten.- Forward client context with
Host,X-Forwarded-For,X-Forwarded-Proto, andX-Real-IP, and configure the app to trust them. - Tune timeouts (
proxy_read_timeout), body size (client_max_body_size), and buffering (disable it for streaming). - WebSockets need
Upgrade/Connectionheaders and HTTP/1.1; SSE needs buffering off. - 502 means the backend is unreachable or crashed; 504 means the backend was too slow.
Quick Example
A typical app server config: static assets from disk, API and app requests to a backend, WebSockets supported.
Core Concepts
Server Blocks and Location Matching
Nginx picks a server block by listen address and port, then by server_name (exact, wildcard, regex, or default_server). Within it, location selection follows specific rules:
Surprises usually come from regex locations overriding a prefix you expected to win. nginx -T prints the full effective configuration.
proxy_pass and URI Rewriting
Whether proxy_pass includes a URI changes the forwarded path:
With a URI (even just /), the matched location prefix is replaced by that URI. Mismatched slashes (location /api with proxy_pass http://backend/) produce paths like //users.
Forwarded Headers
The backend sees Nginx as the client unless you pass context:
Host $host: the original hostname, for virtual hosting and absolute URLs.X-Forwarded-For $proxy_add_x_forwarded_for: the client IP chain.X-Forwarded-Proto $scheme:httpswhen TLS terminates at Nginx, so apps generatehttps://redirects.X-Real-IP $remote_addr: a single client IP.
The application must be configured to trust these headers only from the proxy (Uvicorn --forwarded-allow-ips, Express trust proxy, Django SECURE_PROXY_SSL_HEADER, Spring server.forward-headers-strategy). Behind another load balancer, use the realip module (set_real_ip_from, real_ip_header) to recover the real client IP. See FastAPI deployment.
Timeouts, Buffering, and Body Size
Buffering is great for normal responses, and wrong for streaming. Disable it for SSE, long polling, and streamed LLM responses, or have the app send X-Accel-Buffering: no.
WebSockets and Streaming
WebSockets start as an HTTP/1.1 request with Upgrade: websocket. Nginx must pass the upgrade explicitly (proxy_http_version 1.1, plus the Upgrade and Connection headers) and use a long proxy_read_timeout, or idle connections are closed after 60s. See WebSockets and server-sent events.
Best Practices
Serve Static Assets From Nginx or a CDN
Let Nginx serve built assets with long cache lifetimes (fingerprinted filenames plus immutable), and keep application workers for dynamic requests. For global audiences, put a CDN in front.
Keep Upstream Connections Alive
keepalive in the upstream block, with proxy_http_version 1.1 and an empty Connection header for non-WebSocket locations, reuses connections to backends and reduces latency and socket churn.
Use Includes for Shared Proxy Settings
Put common proxy_set_header and timeout directives in a snippet (include snippets/proxy.conf;). Note that proxy_set_header directives in a location replace, not add to, those inherited from the server level, which is a common source of missing headers.
Test and Reload Safely
Run nginx -t before every reload, and use nginx -s reload (or systemctl reload nginx) for zero-downtime config changes. Keep configuration in version control.
Common Mistakes
Wrong Trailing Slash Combination
Match the slashes: location /app/ with proxy_pass http://backend/.
Missing X-Forwarded-Proto
Behind TLS termination without X-Forwarded-Proto, apps think requests are HTTP, generate http:// redirects, set non-Secure cookies, and may loop between HTTP and HTTPS.
Default Timeouts for Long Requests
Report generation or AI streaming endpoints taking longer than 60 seconds get cut off with 504. Raise proxy_read_timeout only on those locations, or better, make the work asynchronous.
FAQ
What's the difference between a reverse proxy and a load balancer?
A reverse proxy accepts client requests and forwards them to backend servers, adding features like TLS termination, caching, and header manipulation. A load balancer distributes requests across multiple backends. Nginx does both: a reverse proxy with an upstream block of several servers is a load balancer.
What causes a 502 Bad Gateway from Nginx?
Nginx couldn't get a valid response from the backend: the app isn't running, it's listening on a different port or socket, it crashed mid-request, or it closed the connection. Check the Nginx error log and the application logs. A 504 means the backend didn't respond within the timeout.
How do I proxy WebSockets with Nginx?
Use proxy_http_version 1.1, pass Upgrade $http_upgrade, and set Connection to upgrade (via a map so non-WebSocket requests get close or an empty value). Increase proxy_read_timeout so idle sockets aren't dropped, or send application-level pings.
Should I use Nginx or a cloud load balancer?
Often both. A cloud load balancer handles public ingress, TLS, and scaling across instances, while Nginx on each host (or as a Kubernetes ingress controller) handles app-specific routing, static files, and buffering. For simple containerized apps, the platform's load balancer or ingress may be enough on its own.
Related Topics
- Nginx — The web server overview
- Nginx Load Balancing — Distributing traffic across upstreams
- Nginx TLS — HTTPS termination
- Nginx Caching — Caching proxied responses
- WebSockets — Proxying persistent connections
- HTTP — Headers and protocol behavior