Trusted Proxies

When OxPHP runs behind a reverse proxy (Kubernetes Ingress, Cloudflare, AWS ALB, nginx), every request arrives from the proxy's IP address. Without trusted proxy configuration, rate limiting, access logging, and $_SERVER['REMOTE_ADDR'] all see the proxy IP instead of the real client.

Configuration

bash
# Comma-separated CIDR list TRUSTED_PROXIES="10.0.0.0/8,172.16.0.0/12,192.168.0.0/16" # Shorthand: all RFC-1918 + loopback + link-local (IPv4 and IPv6) TRUSTED_PROXIES="private"
Note

When unset, OxPHP ignores all forwarding headers. That is the safe default.

How it works

When a request arrives from a trusted IP, OxPHP inspects forwarding headers in priority order:

  1. Forwarded (RFC 7239) — the standardized header
  2. X-Forwarded-For / X-Forwarded-Proto / X-Forwarded-Host / X-Forwarded-Port — de-facto fallback

If the Forwarded header is present, X-Forwarded-* headers are ignored.

Client IP extraction

OxPHP uses the rightmost-non-trusted algorithm — the same approach used by nginx (real_ip_recursive on), Caddy, Traefik, and Apache:

text
X-Forwarded-For: 203.0.113.50, 172.16.1.1, 10.0.0.5 TCP peer: 10.0.0.1 (trusted) Walk right-to-left: 10.0.0.5 → trusted → skip 172.16.1.1 → trusted → skip 203.0.113.50 → NOT trusted → client IP

This prevents spoofing via prepended values. An attacker can add fake IPs to the left, but the rightmost untrusted IP was set by the last trusted proxy in the chain.

What changes

When TRUSTED_PROXIES is configured and the connecting IP is trusted:

Component Without trusted proxies With trusted proxies
$_SERVER['REMOTE_ADDR'] Proxy IP Real client IP
$_SERVER['REMOTE_PORT'] Proxy source port Client port from Forwarded: for=ip:port, otherwise 0
$_SERVER['HTTPS'] Based on OxPHP's TLS config From Forwarded: proto= or X-Forwarded-Proto
$_SERVER['REQUEST_SCHEME'] http or https from TLS From forwarded protocol
$_SERVER['SERVER_NAME'] From Host header From Forwarded: host= or X-Forwarded-Host
$_SERVER['SERVER_PORT'] From Host header From X-Forwarded-Port, else port of X-Forwarded-Host / Forwarded: host=, else 443/80
Rate limiting Per-proxy IP Per-client IP
Access log Proxy IP Real client IP

REMOTE_PORT is 0 behind a proxy unless an RFC 7239 Forwarded: for=ip:port node carries the client's source port. X-Forwarded-For has no port field, so the rewritten value cannot be reconstructed and is zeroed instead of guessed.

private networks

The private shorthand includes:

Network Description
10.0.0.0/8 Class A private
172.16.0.0/12 Class B private
192.168.0.0/16 Class C private
127.0.0.0/8 Loopback
169.254.0.0/16 Link-local
::1/128 IPv6 loopback
fc00::/7 IPv6 unique local
fe80::/10 IPv6 link-local

Security

  • Safe default — without TRUSTED_PROXIES, no forwarding headers are processed
  • CIDR validation — invalid values in TRUSTED_PROXIES cause a startup error
  • Spoofing resistance — the rightmost-non-trusted algorithm ignores attacker-prepended values
  • Requests from untrusted IPs have their forwarding headers ignored entirely

See also