Proxies
A proxy is an intermediary that terminates one connection and opens another on your behalf, so the party on the far side sees the proxy's address instead of yours — which end of the wire gets hidden is the only thing that separates a forward proxy from a reverse proxy.
The mechanism: who does the far side see?
Because the proxy makes its own outbound connection, the true endpoint is invisible to whoever is on the other side. Point that hiding in one direction and you get one kind of proxy; point it the other way and you get the other.
- Forward proxy — sits in front of clients and speaks to the wider internet on their behalf. The origin server sees the proxy's IP, not the user's. Deployed by the client side (a corporate network, a school, a VPN egress). Classic jobs: cache popular pages, filter/block URLs, log egress traffic, add or strip headers.
- Reverse proxy — sits in front of servers and answers on their behalf. The client sees the proxy's IP (
shop.example.com), never the backend's private address. Deployed by the server side. Classic jobs: TLS termination, caching, compression, health-checked routing to backends.
A transparent proxy intercepts traffic without the client configuring it (the network silently reroutes port 80/443 through it — think a hotel Wi-Fi captive portal); an anonymizing proxy is one a client deliberately points at to conceal its own identity. Transparent-vs-anonymizing is about client awareness; forward-vs-reverse is about which side deployed it.
Worked trace: one HTTPS request through a reverse proxy
A browser loads https://shop.example.com/cart. The public DNS name resolves to an nginx reverse proxy at 203.0.113.10:443. Two identical backends live on a private network: 10.0.1.7:8080 and 10.0.1.8:8080. Follow the request:
| # | Where | What happens (real values) |
|---|---|---|
| 1 | Client → proxy | TCP + TLS handshake to 203.0.113.10:443. TLS is terminated at the proxy — it holds the shop.example.com cert. Client never learns a backend IP. |
| 2 | Proxy | Cache lookup for key GET /cart + cookie. /cart is per-user → MISS. Proxy must go to origin. |
| 3 | Proxy | Rewrites the request for the backend: opens plaintext HTTP to a chosen upstream and injects X-Forwarded-For: 198.51.100.23 (the real client IP), X-Forwarded-Proto: https, Host: shop.example.com. |
| 4 | Proxy → backend | Picks 10.0.1.7:8080 (round-robin). If a health check had marked .7 down, it would use .8 instead — that selection step is load balancing riding on the proxy. |
| 5 | Backend | App reads X-Forwarded-Proto: https, builds an absolute redirect as https://… (not http://), returns 200 with Cache-Control: private. |
| 6 | Proxy → client | Sees private → does not cache. Re-encrypts under TLS and streams the body back over the original connection. The browser only ever saw 203.0.113.10. |
Now change step 2 to a cacheable asset, GET /logo.png: the first request is a MISS and populates the cache; the next 10 000 requests are served from the proxy's memory/disk in step 2 and never touch a backend. That is the entire economic argument for an edge reverse proxy.
Collapsed forwarding (request coalescing)
Suppose 500 clients ask for the same uncached /logo.png in the same 50 ms. A naive proxy fires 500 identical backend fetches — a cache stampede. Collapsed forwarding sends one fetch, parks the other 499 on that in-flight request, and fans the single response out to all of them. In nginx this is literally proxy_cache_lock on;; Varnish calls the parked requests a waiting list. The disk/origin is read once, not 500 times.
The real interview trap: reverse proxy vs load balancer vs API gateway
These blur because a load balancer and an API gateway are both specialized reverse proxies — every one of them is an L7 intermediary that terminates the client connection and forwards. They are distinguished by their defining job, not by being different species. A single nginx or Envoy process can play all three roles at once, which is exactly why the terms get muddled.
- Reverse proxy — the general category. Defining job: be a controlled front door for origin(s) — TLS, caching, compression, header rewriting, hiding topology. It may front just one backend.
- Load balancer — a reverse proxy whose defining job is distribution + availability across N identical backends: health checks, connection draining, algorithms (round-robin, least-connections). It is deliberately dumb about your API; it does not care what
/cartmeans. Can run at L4 (raw TCP, no HTTP awareness) or L7. - API gateway — a reverse proxy whose defining job is application policy for microservices: authenticate/authorize each call, rate-limit per API key, route by path to different services (
/cart→cart-svc,/search→search-svc), aggregate responses, translate protocols (REST↔gRPC). It is deeply API-aware.
The clean mental split: a load balancer spreads load across copies of the same thing; an API gateway routes and polices calls to different things. In a typical stack a request flows client → reverse proxy / LB at the edge → API gateway → services, and the LB reappears behind the gateway to spread each service's replicas.
When to use it / when NOT to
Reach for a reverse proxy when any of these signal it: you need TLS termination in one place, edge caching/compression, a stable public hostname while backends churn, or you want to hide internal topology. Concrete tell: "I want browsers to hit one address and never see my server IPs."
Prefer a plain load balancer over a fuller proxy/gateway when you just have N identical stateless replicas and need availability + spread with minimal latency — especially an L4 LB when you want raw TCP throughput, no HTTP parsing, and per-connection overhead near zero (databases, gRPC streams, non-HTTP protocols). Choosing an API gateway here would add per-request auth/routing latency and an operational surface you don't need.
Reach for an API gateway when you have many different services behind one public API and cross-cutting policy you refuse to reimplement in each service: authN/Z, per-client rate limits, path-based routing, response aggregation, protocol translation. Prefer a bare reverse proxy/LB instead when you have one or a few homogeneous backends — a gateway is then just extra classes, extra latency, and a fatter blast radius for no gain.
Trade-off ledger. What every proxy buys you: a central control point, decoupling, caching, TLS offload — and, because TLS now terminates in one place, the proxy can reuse TLS session tickets/IDs so returning clients resume without the costly asymmetric handshake, which is what lets one proxy front many thousands of RPS without its CPU disappearing into key exchanges. What it costs: an extra network hop (added p50/p99 latency), a new component to run and patch, a potential single point of failure (so the proxy tier itself must be replicated), and — the subtle one — it terminates the connection, so anything the app needs about the original client (IP, scheme, protocol) must be forwarded explicitly or it is simply lost.
Choose THIS when: the edge job is offload/caching/hiding → reverse proxy; pure distribution across copies → load balancer (L4 if non-HTTP or latency-critical); app-aware policy over a fleet of distinct services → API gateway. In practice you layer them, not pick one.
Pitfalls
- Lost client IP. After termination the backend sees the proxy's IP, so rate-limits and geo-logic all key on one address. Fix: forward
X-Forwarded-For— but the app must trust it only from your proxy, or clients spoof the header to forge their IP. - Redirect loops from mismatched scheme. TLS ends at the proxy, so the backend sees plain HTTP and emits
http://redirects; the browser upgrades to HTTPS, hits the proxy, downgrades again — infinite loop. Fix: sendX-Forwarded-Proto: httpsand make the app honor it. - Response buffering kills streaming. Proxies buffer the whole response by default; that breaks Server-Sent Events, chunked progress, and long polls (client sees nothing until the end). Disable buffering on those routes (nginx
proxy_buffering off;). - Timeout mismatch → 502/504. Backend takes 40 s but the proxy's upstream read timeout is 30 s → the proxy kills a request the backend is still happily processing, and the user sees a gateway error. Align the timeouts and add retries carefully (retrying non-idempotent POSTs double-charges).
- Open forward proxy = abuse relay. A forward proxy with no auth/allow-list lets strangers launder traffic through your IP. Lock it down.
- The proxy is a SPOF. One reverse proxy in front of a healthy fleet still means one box whose crash takes everything down. Run at least two behind a VIP/DNS.
Takeaways
- Forward proxy hides the client (deployed client-side); reverse proxy hides the server (deployed server-side). Same mechanism — a terminated connection — aimed in opposite directions.
- Load balancer and API gateway are both reverse proxies with a specialty: LB = distribute across identical copies; gateway = auth/route across different services. One process can be all three.
- Terminating the connection is the whole trick and the whole hazard — you gain caching/TLS/hiding, but original client IP, scheme, and streaming must be carried forward deliberately or they vanish.
- Any proxy tier you add must itself be redundant, or you have traded scattered risk for one big single point of failure.
Re-authored and deepened for this guide. Sources: Grokking the System Design Interview (Design Gurus) for the forward/reverse framing and collapsed forwarding; NGINX documentation (proxy_cache_lock, proxy_buffering, X-Forwarded-* handling); Cloudflare Learning Center ("What is a reverse proxy?") and NGINX/Kong material on the reverse-proxy vs load-balancer vs API-gateway distinction. Worked trace, decision criteria, and pitfalls are original.
🤖 Don't fully get this? Learn it with Claude
Stuck on Proxies? Open Claude, copy a block below, and it'll teach you this exact concept — visually and interactively.
Build the mental picture, not memorization.
I just read a lesson on **Proxies** (System Design) and want to truly understand it. Explain Proxies from first principles using ONE vivid real-world analogy and a visual mental model — draw it as ASCII art or a clear step-by-step diagram — with a concrete example using real numbers. Then ask me one question to check I got the mental picture, and wait for my reply. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
Socratic — adapts to where you're stuck.
Teach me **Proxies** interactively. Ask me ONE guiding question at a time, wait for my answer, and adapt to my confusion — build the idea with me step by step instead of explaining it all at once. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
Active recall exposes what you missed.
Quiz me on **Proxies** with 5 questions, easy to tricky, ONE at a time. Tell me if each answer is right; at the end, explain clearly what I got wrong and why. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
Intuition + hook + flashcards for long-term memory.
Help me remember **Proxies** for the long term: give the one-sentence intuition, a memorable hook/mnemonic, a tiny worked example, and 3 active-recall flashcards (Q -> A). If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.