backend / networking / 11_proxies_forward_reverse.md

Proxies: Forward vs Reverse

5 interview angles 5 min read source

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_PROXY env 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-For header. Configure the framework to trust it (SECURE_PROXY_SSL_HEADER, USE_X_FORWARDED_HOST in Django; ProxyHeadersMiddleware in 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.