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
# 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"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:
Forwarded(RFC 7239) — the standardized headerX-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:
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 IPThis 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_PROXIEScause 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
- Rate Limiting — per-IP rate limiting uses the resolved client IP
- Access Logging —
remote_ipfield shows the resolved client IP - Configuration Reference — all environment variables