Proxies: Forward vs Reverse
A proxy is an intermediary that forwards requests on behalf of someone. The “forward” vs “reverse” distinction is about who it represents:
| Forward proxy | Reverse proxy | |
|---|---|---|
| Sits in front of | clients | servers |
| Acts on behalf of | the client | the server |
| Client knows it’s there? | usually yes (configured) | no (looks like the server itself) |
| Examples | corporate web proxy, Squid, your VPN exit, Burp Suite | nginx, Envoy, HAProxy, ALB, CloudFlare |
| Typical use | content filtering, caching, anonymity, egress control | TLS termination, routing, load balancing, caching, WAF |
Forward proxy — the client-side proxy
[ Browser ] ──▶ [ Forward proxy ] ──▶ [ example.com ]
configured to use visible to example.com
The client is configured to send all outbound HTTP through the proxy. The proxy can:
- Filter URLs (block social media at the office).
- Cache responses (early-2000s ISP caches).
- Authenticate users (bring corporate identity to outbound traffic).
- Hide the real client IP from the destination.
- Modify requests/responses (Burp Suite for security testing).
Configured via:
- Browser proxy settings.
HTTP_PROXY/HTTPS_PROXYenv vars (respected by curl, requests, most CLIs).- PAC files (
http://wpad/wpad.dat). - Transparent interception via DNAT (no client config — packets are silently rerouted).
In Python:
import os
os.environ["HTTPS_PROXY"] = "http://proxy.corp.example:8080"
import requests
requests.get("https://api.example.com/") # goes via proxy
Reverse proxy — the server-side proxy
[ Internet ] ──▶ [ nginx :443 ] ──▶ [ Django :8000 ]
reverse proxy upstream
Clients think they’re talking to the website; in reality, the reverse proxy is in front of N application servers. Benefits:
- TLS termination — handle certs in one place.
- Load balancing — fan out to multiple backends. See 10_load_balancers.md.
- Routing —
/api/v2/*to one pool,/static/*to another or to disk. - Caching — serve repeated GETs from memory without hitting the app.
- Compression — gzip / brotli once at the edge.
- Security — WAF, rate limiting, IP allowlisting, hide internal topology.
- Slow client buffering — the proxy reads the full request before forwarding, so the slow Python worker isn’t tied up reading 200 bytes/sec from a phone on a bad connection.
Practically every Python web app in production has a reverse proxy in front (nginx / ALB / Cloudflare / Envoy). Even a single-server app benefits from one.
Forward proxy in Python (less common)
Configure outbound HTTP via env vars or per-call:
import requests
proxies = {
"http": "http://user:pass@proxy.corp:8080",
"https": "http://user:pass@proxy.corp:8080",
}
requests.get("https://api.example.com/", proxies=proxies)
NO_PROXY="localhost,127.0.0.1,*.internal" excludes destinations from proxying — important so internal calls don’t try to go through a forward proxy that can’t reach internal hosts.
Reverse proxy patterns
nginx in front of Gunicorn
upstream django_app {
server unix:/run/gunicorn.sock;
}
server {
listen 443 ssl http2;
server_name api.example.com;
location /static/ {
alias /var/www/static/;
expires 30d;
}
location / {
proxy_pass http://django_app;
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_read_timeout 60s;
}
}
The Unix socket between nginx and Gunicorn is faster than 127.0.0.1:8000 (no TCP stack overhead). See 06_ports_sockets.md.
TLS termination + plain HTTP backend
The standard pattern. nginx (or ALB) handles certs and listens on 443. Backends listen on plain HTTP on a private subnet. Both inside the VPC, network is trusted.
Sidecar reverse proxy (Envoy)
In service-mesh setups (Istio, Linkerd), every pod has an Envoy sidecar. The app talks to localhost; Envoy handles outbound routing, retries, mTLS, observability. The app code stays simple.
Headers a reverse proxy adds
When the app sees request.META["REMOTE_ADDR"], it’s the proxy’s IP, not the client’s. The proxy adds headers to carry the originals:
| Header | Carries |
|---|---|
X-Forwarded-For |
original client IP (and chain of intermediaries) |
X-Forwarded-Proto |
original scheme (https even though backend got http) |
X-Forwarded-Host |
original Host header |
Forwarded (RFC 7239) |
structured replacement for the above; less commonly used |
In Django:
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
USE_X_FORWARDED_HOST = True
In FastAPI / Starlette, mount ProxyHeadersMiddleware. Only trust these headers when behind a known proxy — otherwise clients can spoof them and bypass IP-based logic.
Caching reverse proxy (CDN-lite)
nginx with proxy_cache:
proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=app:10m inactive=60m;
server {
location /api/items {
proxy_cache app;
proxy_cache_valid 200 5m;
proxy_cache_use_stale error timeout updating;
proxy_pass http://backend;
}
}
Cuts backend load for hot read endpoints. For full-on edge caching across regions, use a CDN. See 13_cdn.md.
Common interview confusions
- “Forward and reverse proxy are the same.” — both forward HTTP, but the who-they-represent is opposite. Forward = client’s tool. Reverse = server’s tool.
- “A reverse proxy is a load balancer.” — overlapping but not identical. A reverse proxy may have one upstream; a load balancer always has multiple. nginx is both, depending on how you configure it.
- “VPN and proxy are the same.” — VPN tunnels at L3 (every packet, transparent to apps). Proxy works at L7 (per-application, app must be configured). Both can hide your IP, but mechanically different.
- “X-Forwarded-For is trustworthy.” — only when set by a proxy you trust. If your app sees this header from a direct client (not behind a proxy), it can be anything the client wrote.
Interview angle
- “What’s the difference between forward and reverse proxy?” — forward sits in front of clients (acts on their behalf — corporate filter, anonymizer); reverse sits in front of servers (acts on their behalf — TLS, LB, cache). Same plumbing, opposite role.
- “Why do you put a reverse proxy in front of a Python app?” — TLS termination, slow-client buffering, load balancing, static file serving, compression, WAF/rate limiting, hides internal topology.
- “How do you get the real client IP behind a reverse proxy?” —
X-Forwarded-Forheader. Configure the framework to trust it (SECURE_PROXY_SSL_HEADER,USE_X_FORWARDED_HOSTin Django;ProxyHeadersMiddlewarein FastAPI). Only trust when actually behind a known proxy. - “When would you use a forward proxy in production?” — egress filtering / monitoring (corporate networks), shared outbound IP for whitelisting with a third-party API, scraping with rotating IPs.
- “nginx vs Envoy vs ALB as a reverse proxy?” — nginx is the classic single-server proxy + static server. Envoy is the modern service-mesh-friendly proxy with rich observability and dynamic config. ALB is AWS managed L7 LB — less flexible, fully managed. Pick by ops model.