Decision records

ADR-0022: Trusted proxies (X-Forwarded-For, PROXY protocol)

  • Status: Accepted (2026-10-06)

Context

Behind a load balancer every connection comes from the balancer: per-source limits (ADR-0013) would throttle all clients together, access logs and REMOTE_ADDR would show the balancer. Client-supplied X-Forwarded-For must never be believed blindly.

Decision

  • server.trusted_proxies: CIDRs of peers allowed to report the client address. Reloadable.
  • Header mode (any listener): for requests whose TCP peer is trusted, the client is the right-most X-Forwarded-For entry that is not itself a trusted proxy (entries further left may be forged by the client). The chain forwarded upstream is the received chain plus the peer. From untrusted peers the header is ignored and replaced.
  • PROXY protocol (listener.proxy_protocol: true, requires trusted_proxies): v1 and v2 headers, read before TLS/HTTP within header_read_timeout, never past the header. Peers outside trusted_proxies are closed immediately (untrusted_proxy); missing or malformed headers close the connection (proxy_protocol). v1 UNKNOWN, v2 LOCAL and non-TCP families keep the peer address. PROXY listeners admit connections per client after reading the header (the accept loop never waits).
  • The effective client address feeds the per-source request limiter, the access log, RequestContext.remote_addr (PHP REMOTE_ADDR, Node/Python via X-Forwarded-For) and diagnostics. Connections from trusted peers are exempt from the per-source connection limit (a balancer multiplexes many clients); their requests are limited per client.
  • Forwarded (RFC 7239) is not parsed.