Frontend Labs
⋯

65. Proxies: forward, reverse, CONNECT, and interception

65.1 The two directions

FORWARD PROXY (on the client's side; the client knows about it)
   client -> [proxy] -> any server on the internet
   uses: corporate egress control, filtering, caching, anonymity

REVERSE PROXY (on the server's side; the client does not know)
   client -> [proxy] -> your back-end servers
   uses: load balancing, TLS termination, caching, WAF   (Ch 40)

Part XI covered reverse proxies. This section is about the forward direction, which developers meet as "why does this only fail on the corporate network?".

65.2 The CONNECT method

A forward proxy can't read HTTPS, so for TLS it builds a blind TCP tunnel:

Client -> proxy:   CONNECT example.com:443 HTTP/1.1
                   Host: example.com:443
                   Proxy-Authorization: Basic ...        (if required)
Proxy  -> client:  HTTP/1.1 200 Connection Established

-> from here the proxy blindly relays bytes; the TLS handshake happens
   end-to-end between client and origin. The proxy sees only the hostname
   (from CONNECT) and the traffic volume.

Consequences: the proxy can allow/deny by hostname but cannot inspect content — unless it performs TLS interception (below).

65.3 How clients find a proxy

Mechanism How
Environment variables http_proxy, https_proxy, no_proxy (respected by curl, most CLIs, many SDKs)
PAC file A JavaScript function FindProxyForURL(url, host) returning "PROXY host:port" / "DIRECT". Allows per-destination rules.
WPAD Auto-discovery of the PAC file via DHCP option 252 or DNS. Convenient; historically a nice attack vector.
OS/browser settings System proxy configuration

no_proxy is a classic footgun: its syntax is inconsistent across tools (leading dots, CIDR support, wildcard support all vary), so "it works in curl but not in my app" is common.

65.4 TLS interception (corporate MITM)

To inspect HTTPS, an enterprise proxy terminates TLS and re-encrypts:

client ==TLS(corp CA cert)==> [proxy decrypts, inspects] ==TLS(real cert)==> origin

This requires installing the corporate root CA on every device. Effects you will hit:

  • Certificate pinning breaks (apps that pin to a specific CA/key refuse to connect).
  • Non-browser clients fail because they use their own trust store: Python (certifi), Node, Java (cacerts), Go, Docker, package managers. Each needs the corporate CA added separately — this is the single most common cause of "SSL certificate problem: unable to get local issuer certificate" in corporate environments.
  • mTLS to external services breaks (the proxy can't present your client cert).
  • HTTP/3 usually isn't supported by the proxy, so QUIC gets blocked and clients fall back to TCP.

65.5 The Forwarded / X-Forwarded-* headers

Once a proxy is in the path, the back end sees the proxy's address. Identity is carried in headers:

X-Forwarded-For: 203.0.113.9, 198.51.100.7    (client, then each proxy, left to right)
X-Forwarded-Proto: https
X-Forwarded-Host: example.com
Forwarded: for=203.0.113.9;proto=https;host=example.com    (RFC 7239, the standard one)

Security rule: these headers are client-supplied and trivially spoofed. Only trust them from known proxy IPs, and strip or overwrite them at your edge. Count from the right, using the number of proxies you actually operate. Getting this wrong turns your rate limiter and IP allowlist into decoration.

For TCP-mode (L4) proxies that can't add headers, the PROXY protocol (Ch 38.11) carries the original addresses ahead of the byte stream.

65.6 Real-world example

A team's CI pipeline failed only inside the corporate network with x509: certificate signed by unknown authority. curl worked (it had been configured with --cacert), the browser worked (OS trust store had the corporate CA), but Go and Python did not. Root cause: TLS interception plus three separate trust stores. Fix: bake the corporate CA into the CI container image and set SSL_CERT_FILE/NODE_EXTRA_CA_CERTS/REQUESTS_CA_BUNDLE, plus a no_proxy entry for internal registries.

Try it yourself

# use a proxy explicitly
export https_proxy=http://proxy.corp:3128
curl -v https://example.com 2>&1 | grep -iE 'CONNECT|Proxy|HTTP/1.1 200'

# what does my environment think?
env | grep -i proxy

# which CA store is a tool using?
curl -v https://example.com 2>&1 | grep -i 'CAfile\|CApath'
python3 -c "import certifi; print(certifi.where())"

# detect TLS interception: is the issuer your corporate CA?
echo | openssl s_client -connect example.com:443 2>/dev/null \
  | openssl x509 -noout -issuer

Generated from real-life-example.md · 8 Oct 2026 16:36