Configuring trusted proxies
Behind a CDN, load balancer, or reverse proxy, the web server sees the proxy's IP address instead of the visitor's. TRUSTED_PROXY tells NGINX, Apache, or FrankenPHP which proxies to trust and which header holds the real client IP, so your logs, rate limits, and IP rules use the right address.
Choose a TRUSTED_PROXY value
Set TRUSTED_PROXY on the fpm-nginx, fpm-apache, or frankenphp image:
services:
php:
image: serversideup/php:8.5-fpm-nginx
ports:
- 80:8080
environment:
# cloudflare (default), sucuri, local, or off
TRUSTED_PROXY: "local"
| Value | Trusts | Reads the client IP from |
|---|---|---|
cloudflare (default) | Cloudflare's IP ranges and private networks | CF-Connecting-IP (FrankenPHP falls back to X-Forwarded-For) |
sucuri | Sucuri's IP ranges and private networks | X-Forwarded-For |
local | Private networks only | X-Forwarded-For |
off | Nothing. The web server logs the address that connected to it. |
"Private networks" means 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 127.0.0.0/8, ::1, and fd00::/8, which covers Docker networks. Every option except off includes them. If you run Traefik or Caddy in Docker behind Cloudflare, cloudflare already covers both.
Use local when your own reverse proxy is the only thing in front of the container. cloudflare won't work there. NGINX and Apache only read CF-Connecting-IP, which doesn't exist without Cloudflare. They keep the proxy's IP.
The cloudflare option reads CF-Connecting-IP because Cloudflare sets it on every request and proxies like Traefik pass it through unchanged. Many proxies rewrite X-Forwarded-For unless you configure them to trust Cloudflare's ranges.
What Laravel still needs
The web server passes the visitor's IP to PHP as REMOTE_ADDR. That makes request()->ip() correct with no Laravel configuration.
When your proxy terminates HTTPS, Laravel also needs to know the original scheme so it generates https:// URLs. Trust only the X-Forwarded-Proto header in Laravel's trusted proxy configuration:
use Illuminate\Http\Request;
->withMiddleware(function (Middleware $middleware) {
$middleware->trustProxies(
at: '*',
headers: Request::HEADER_X_FORWARDED_PROTO,
);
})
at: '*' trusts whatever address connected to PHP, which is the visitor once the web server has replaced REMOTE_ADDR. Use it only when visitors can't reach the container directly and every request passes through your proxy. Don't add HEADER_X_FORWARDED_FOR. Cloudflare appends to X-Forwarded-For instead of replacing it. Laravel would then return an address the visitor wrote into that header.Keep the proxy chain closed
A trusted proxy configuration only protects you if traffic can't skip the proxy:
- Accept traffic only from your CDN. A client that connects straight to your reverse proxy can send a forged
CF-Connecting-IPheader, and the proxy passes it along. Allow only your CDN's IP ranges at the edge, for example with a firewall rule or Traefik'sipAllowListmiddleware. - Match the option to your setup. Don't use
cloudflareorsucuriunless traffic comes through that service. - Expect FrankenPHP to resolve differently. FrankenPHP uses Caddy's
trusted_proxies_strictmode, which readsX-Forwarded-Forfrom right to left and stops at the first untrusted address. When every address in the chain is trusted (a client on a private network behind a private proxy), FrankenPHP reports the proxy's address, while NGINX and Apache report the leftmost address.
CDN IP ranges change. The ranges are part of the image, so pull new images regularly. If you depend on them, compare them with your provider's published list.
Add your own provider
For a provider that isn't built in, such as AWS ALB or Fastly, add a file to the image and set TRUSTED_PROXY to its name without the extension:
| Variation | File |
|---|---|
fpm-nginx | /etc/nginx/trusted-proxy/<name>.conf |
fpm-apache | /etc/apache2/trusted-proxy/<name>.conf |
frankenphp | /etc/frankenphp/trusted-proxy/<name>.caddyfile |
Include the private network ranges so container-to-container traffic still resolves. This example uses 3.0.0.0/8 as a stand-in; replace it with your provider's published ranges.
FROM serversideup/php:8.5-fpm-nginx
COPY --chmod=644 docker/trusted-proxy/nginx/aws-alb.conf /etc/nginx/trusted-proxy/aws-alb.conf
For Apache or FrankenPHP, copy the matching file to the path in the table above. To try a file without rebuilding, mount it read-only at the same path instead of using COPY.
Still seeing the proxy's IP
- If the container's access log shows your proxy's address, that address isn't in the ranges for the option you chose. Pick another option, or add a file for your provider as shown above.
- Check the header. Cloudflare sends
CF-Connecting-IP; most other proxies sendX-Forwarded-For. - If the access log is right but
request()->ip()is wrong, check that Laravel isn't trustingX-Forwarded-For(see what Laravel still needs).