Proxy and Reverse Proxy
A forward proxy acts for clients; a reverse proxy acts for servers. Same mechanism, opposite direction. See what reverse proxies do, how the backend loses the client's IP, and how to trust X-Forwarded-For safely.
🎯 What you'll learn
- Forward vs reverse proxies
- What reverse proxies do: TLS, routing, caching, buffering
- Losing the real client and forwarding headers
- Proxy chains, trust, and the API gateway pattern
📚 Before you start
1Same Mechanism, Opposite Direction
A proxy is any intermediary that sits between a client and a server, forwarding requests and responses on someone's behalf. "Forward proxy" and "reverse proxy" describe the exact same relaying mechanism — the only difference is which side it's hiding:
FORWARD PROXY (hides the CLIENT from the server)
Client A --\
Client B ---> [ Forward Proxy ] ---> Server
Client C --/
The server sees ONE proxy IP. It has no idea which of A/B/C actually asked.
REVERSE PROXY (hides the SERVER(s) from the client)
/--> Backend 1
Client ---> [ Reverse Proxy ]---> Backend 2
\--> Backend 3
The client sees ONE proxy IP/domain. It has no idea which backend actually answered.A forward proxy is configured and known to the client (corporate egress filtering, a VPN, an anonymizing proxy) and is typically invisible to the server. A reverse proxy is configured and known to the server operator (nginx, an API gateway, a CDN edge) and is typically invisible to the client, who believes they're talking directly to "the site."
2What a Reverse Proxy Actually Does
A reverse proxy is rarely just a dumb relay — it's usually doing several jobs at once, all invisibly to the client:
| Responsibility | What it means |
|---|---|
| TLS termination | client's HTTPS connection ends at the proxy; proxy-to-backend traffic may be plain HTTP on a trusted internal network |
| Load balancing | spreads requests across multiple backend instances (round-robin, least-connections, etc.) |
| Routing | sends /api/* to one backend, /* (everything else) to another, based on path or Host |
| Caching | can serve a cached response itself without ever forwarding to a backend (see 05-caching_and_compression) |
| Compression | can compress an uncompressed backend response before it reaches the client |
| Rate limiting / auth | can reject or throttle requests before a backend ever sees them |
# nginx reverse proxy: path-based routing to two backends, one upstream
upstream api_backend {
server 10.0.0.11:4000;
server 10.0.0.12:4000; # load-balanced across both
}
server {
listen 443 ssl;
server_name northwind.dev;
location /api/ {
proxy_pass http://api_backend;
}
location / {
proxy_pass http://10.0.0.20:3000; # web app backend
}
}3The Problem: the Backend Loses the Real Client
Once a reverse proxy is in the path, every request the backend receives has the proxy's own IP as its TCP source address — from the backend's point of view, every request looks like it came from the same one machine:
Client (203.0.113.7) -> Reverse Proxy (10.0.0.1) -> Backend
Backend's connection info: remote address = 10.0.0.1 (the proxy, not the client!)This breaks anything that depends on the real client IP: per-IP rate limiting, geolocation, abuse logging, "last login IP" — all of it, unless the proxy explicitly forwards the original information in headers.
4Forwarding Headers
X-Forwarded-For: 203.0.113.7
X-Forwarded-Proto: https
X-Forwarded-Host: northwind.dev
Via: 1.1 nginx-edge-01| Header | Carries |
|---|---|
X-Forwarded-For | the original client IP (and, if there are multiple proxies in a chain, a comma-separated list, oldest first) |
X-Forwarded-Proto | whether the original client connection was http or https — matters because TLS is often terminated at the proxy, so the backend's own connection may be plain http even though the real client used https |
X-Forwarded-Host | the Host header the client originally sent, in case the proxy rewrites it before forwarding |
Via | a general-purpose "this passed through proxy X" trace header, standardized (unlike the X-Forwarded-* family, which is a de facto convention, not an RFC standard — the standardized replacement is Forwarded, e.g. Forwarded: for=203.0.113.7;proto=https, though X-Forwarded-* remains far more common in practice) |
A backend must be explicitly configured to read X-Forwarded-For (or Forwarded) instead of the raw TCP source address wherever it wants the real client's IP — this is not automatic, and most web frameworks default to trusting the raw socket address only.
5Proxy Chains and Trust
X-Forwarded-For can accumulate multiple hops:
X-Forwarded-For: 203.0.113.7, 10.0.0.1, 10.0.0.2
^ original ^ proxy 1 ^ proxy 2 (closest to backend)Each proxy in the chain is supposed to append its own view of the previous hop's address rather than overwrite the header — which means the leftmost value is only trustworthy if every proxy in the chain is trusted to append correctly and never forge it. A client can freely send their own fabricated X-Forwarded-For header on the original request; a correctly configured edge proxy should strip or overwrite any client-supplied value before appending the real connecting IP, otherwise the backend ends up trusting attacker-controlled input for exactly the kind of decision (rate limiting, geo-restriction, abuse blocking) this header exists to support.
6The API Gateway Pattern
When a reverse proxy fronts multiple independent backend services behind one public domain, it's commonly called an API gateway — the routing responsibility from section 2 becomes the primary job:
Client -> https://northwind.dev/*
/ -> web app backend
/api/* -> REST API backend
/ws/* -> WebSocket backend (see note below)
/admin/* -> internal admin backend, extra auth enforced at the gatewayA single public domain and a single TLS certificate front several independently deployable services, each of which can be scaled, deployed, or replaced without the client ever knowing the topology changed. WebSocket traffic needs explicit proxy support for the Upgrade/Connection: upgrade handshake (a plain HTTP reverse-proxy config that doesn't forward these headers will silently break WebSocket connections while ordinary HTTP routes through it fine).
⚠️ Common Pitfalls
- Confusing forward and reverse proxy direction. The mechanism is identical; only "which party is anonymized" differs — mixing them up leads to configuring the wrong side's trust boundary.
- Trusting
X-Forwarded-Forwithout stripping client-supplied values first. Any client can send a fakeX-Forwarded-Foron the original request; only the edge proxy's own appended value is trustworthy, and only if every proxy in the chain enforces this. - Forgetting
X-Forwarded-Proto. A backend that redirects or generates absolute URLs based on its own (often plain-HTTP) connection to the proxy can emithttp://links even though the real client is onhttps://, causing mixed-content errors or redirect loops. - Not forwarding
Upgrade/Connectionheaders for WebSocket routes. Ordinary HTTP traffic works fine through a proxy misconfigured this way, masking the bug until a WebSocket-dependent feature is tested. - Logging or rate-limiting on the raw TCP source address instead of the forwarded client IP. Every request appears to come from the proxy, making per-client analysis meaningless without reading the forwarding headers.
📋 Quick Reference
Forward proxy -> hides the CLIENT from the server; configured by/known to the client
Reverse proxy -> hides the SERVER(s) from the client; configured by/known to the server operator
Reverse proxy jobs: TLS termination, load balancing, routing, caching, compression, rate limiting
X-Forwarded-For -> original client IP (comma-separated chain if multiple proxies)
X-Forwarded-Proto -> http/https of the ORIGINAL client connection (TLS may be terminated upstream)
X-Forwarded-Host -> original Host header, if rewritten before forwarding
Via -> standardized hop trace; Forwarded is the standardized alternative to X-Forwarded-*
Only trust X-Forwarded-For if every hop strips/overwrites client-supplied values before appending
API gateway -> reverse proxy fronting multiple independent backend services on one public domain🧠 Key takeaways
- Forward proxy: clients → proxy → internet (egress control). Reverse proxy: internet → proxy → your servers.
- Reverse proxies do TLS termination, routing, load balancing, caching, compression, buffering, rate limiting.
- Backends see the proxy's IP; pass
X-Forwarded-For/Proto/Hostor the standardForwardedheader. - Trust forwarding headers only from known proxies, counting hops from the right.
🎤 Interview Q&A
Q1What's the difference between a forward proxy and a reverse proxy?core▾
A forward proxy acts for clients (corporate egress proxy, filtering, anonymity): servers see the proxy. A reverse proxy acts for servers (Nginx, Envoy, HAProxy, CDNs): clients talk to it and it forwards to backends.
- Is a load balancer a reverse proxy? An L7 load balancer is one.
Q2What can a reverse proxy do for you?core▾
TLS termination, load balancing, caching, compression, rate limiting, authentication, request routing by path/host, serving static files, adding security headers, and hiding backend topology.
- Example Nginx use? Terminate TLS and route
/apito app servers and/to static files.
Q3What's an API gateway compared with a reverse proxy?core▾
A reverse proxy with API-specific features: authentication/authorization, rate limiting per client, request transformation, API keys, analytics and developer portal integration (Kong, AWS API Gateway, Apigee).
- Risk of a gateway? Becoming a bottleneck or a place for business logic it shouldn't hold.
Q4What headers should a reverse proxy add?core▾
X-Forwarded-For (client IP), X-Forwarded-Proto (original scheme), X-Forwarded-Host, or the standard Forwarded, plus request IDs. Backends must trust these only from the proxy.
- Why does the app need X-Forwarded-Proto? To build correct redirect URLs and enforce HTTPS behind TLS termination.
Q5What is HTTP request smuggling?senior▾
When a front-end proxy and back-end server disagree on where a request ends (conflicting Content-Length and Transfer-Encoding), an attacker can smuggle a hidden request, bypassing controls or poisoning caches. Mitigate by normalizing/rejecting ambiguous requests and using HTTP/2 end to end.
- Which header combination is classic? CL.TE or TE.CL mismatches.
🛠️ Practice exercises
Each exercise is a file in this topic folder. Code exercises are self-checking: run them and make the checks pass.
✅ Check yourself
Answer the questions — feedback is instant.