Skip to content

Commit 5b27e89

Browse files
authored
Add TRUSTED_PROXY support for all variations & Caddy global imports (#643)
* Enhance trusted proxy support across configurations Updated documentation and configuration files to improve trusted proxy handling. Introduced customizable trusted proxy settings for Cloudflare, Sucuri, and local proxies, ensuring accurate IP logging. Removed hardcoded Cloudflare IPs from NGINX and Apache configurations, replacing them with a dynamic inclusion based on the TRUSTED_PROXY environment variable. * Clarify trusted proxy documentation for Cloudflare and Sucuri Updated the documentation to specify that both Cloudflare and Sucuri configurations now automatically include local Docker networks. Added a tip to inform users that they can use the `cloudflare` setting while also trusting local proxies, enhancing clarity on trusted proxy usage. * Add global Caddy configuration support in FrankenPHP Updated the Dockerfile to create a directory for global Caddy configurations and modified the Caddyfile to import additional configuration files from the new caddyfile-global.d directory, enhancing flexibility in Caddy setup. * Fix Dockerfile syntax by correcting line continuation for Caddy configuration paths
1 parent 6c8c8ca commit 5b27e89

File tree

27 files changed

+562
-82
lines changed

27 files changed

+562
-82
lines changed

docs/content/docs/1.getting-started/4.these-images-vs-others.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ Our images run as the `www-data` user by default, following the principle of lea
7272
We also include additional security hardening:
7373
- Disabled dangerous PHP functions by default (but you control them)
7474
- Proper file permissions out of the box
75-
- CloudFlare trusted proxy support for accurate IP logging
75+
- Customizable trusted proxy configuration (Cloudflare, Sucuri, local, or off) for accurate IP logging
7676
- Regular security updates from official PHP base images
7777

7878
### Performance Optimized
Lines changed: 350 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,350 @@
1+
---
2+
head.title: 'Configuring Trusted Proxies - Docker PHP - Server Side Up'
3+
description: 'Learn how to configure trusted proxies to get accurate client IP addresses when running behind load balancers, CDNs, or reverse proxies.'
4+
layout: docs
5+
---
6+
7+
::lead-p
8+
When your application runs behind a load balancer, CDN, or reverse proxy, the web server sees the proxy's IP address instead of the actual visitor's IP. Trusted proxy configuration tells your web server which proxies to trust for forwarding the real client IP.
9+
::
10+
11+
## Why Trusted Proxies Matter
12+
13+
Without proper trusted proxy configuration, your application will:
14+
- Log the proxy's IP address instead of the visitor's real IP
15+
- Have incorrect geolocation data
16+
- Potentially break rate limiting or IP-based security rules
17+
- Show incorrect information in Laravel's `request()->ip()` or similar methods
18+
19+
::warning
20+
Never trust all IP addresses as proxies. Only trust specific proxy IPs that you control or know are legitimate (like your CDN provider).
21+
::
22+
23+
## Available Options
24+
25+
Set the `TRUSTED_PROXY` environment variable to one of the following values:
26+
27+
| Value | Description |
28+
|-------|-------------|
29+
| `cloudflare` (default) | Trusts [Cloudflare's IP ranges](https://www.cloudflare.com/ips/){target="_blank"} **+ local Docker networks** using the `CF-Connecting-IP` header |
30+
| `sucuri` | Trusts [Sucuri's IP ranges](https://docs.sucuri.net/website-firewall/sucuri-firewall-troubleshooting-guide/){target="_blank"} **+ local Docker networks** using the `X-Forwarded-For` header |
31+
| `local` | Trusts only private/local network ranges (Docker networks, localhost) using the `X-Forwarded-For` header |
32+
| `off` | Disables trusted proxy configuration entirely |
33+
34+
::tip
35+
**You don't need to choose between your CDN and local proxies.** All options (except `off`) automatically include Docker's internal network ranges (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`). This means if you're behind Cloudflare *and* running a reverse proxy like Traefik or Caddy in Docker, just use `cloudflare` — it already trusts both.
36+
::
37+
38+
## Quick Start
39+
40+
### Using Cloudflare (Default)
41+
42+
If you're using Cloudflare as your CDN/proxy, you don't need to change anything. The default configuration already trusts Cloudflare's IP ranges:
43+
44+
```yml [compose.yml]
45+
services:
46+
php:
47+
image: serversideup/php:8.5-fpm-nginx
48+
ports:
49+
- "80:8080"
50+
volumes:
51+
- .:/var/www/html
52+
# TRUSTED_PROXY defaults to "cloudflare"
53+
```
54+
55+
### Using Sucuri
56+
57+
If you're using Sucuri as your web application firewall:
58+
59+
```yml [compose.yml]{8}
60+
services:
61+
php:
62+
image: serversideup/php:8.5-fpm-nginx
63+
ports:
64+
- "80:8080"
65+
volumes:
66+
- .:/var/www/html
67+
environment:
68+
TRUSTED_PROXY: "sucuri"
69+
```
70+
71+
### Local/Docker Networks Only
72+
73+
If you're running behind your own reverse proxy (like Traefik or Caddy) on the same Docker network:
74+
75+
```yml [compose.yml]{8}
76+
services:
77+
php:
78+
image: serversideup/php:8.5-fpm-nginx
79+
ports:
80+
- "80:8080"
81+
volumes:
82+
- .:/var/www/html
83+
environment:
84+
TRUSTED_PROXY: "local"
85+
```
86+
87+
### Disabling Trusted Proxies
88+
89+
If you want to handle proxy configuration yourself or your application is directly exposed to the internet:
90+
91+
```yml [compose.yml]{8}
92+
services:
93+
php:
94+
image: serversideup/php:8.5-fpm-nginx
95+
ports:
96+
- "80:8080"
97+
volumes:
98+
- .:/var/www/html
99+
environment:
100+
TRUSTED_PROXY: "off"
101+
```
102+
103+
## Laravel Configuration
104+
105+
While the Docker images handle the web server's trusted proxy configuration, Laravel also needs to know about trusted proxies at the application level.
106+
107+
Laravel includes a `TrustProxies` middleware that you should configure in your application. For most setups using our images, you can trust all proxies since the web server has already validated them:
108+
109+
```php [bootstrap/app.php]
110+
->withMiddleware(function (Middleware $middleware) {
111+
$middleware->trustProxies(at: '*');
112+
})
113+
```
114+
115+
Or if you prefer more control, configure specific headers:
116+
117+
```php [bootstrap/app.php]
118+
->withMiddleware(function (Middleware $middleware) {
119+
$middleware->trustProxies(
120+
at: '*',
121+
headers: Request::HEADER_X_FORWARDED_FOR |
122+
Request::HEADER_X_FORWARDED_HOST |
123+
Request::HEADER_X_FORWARDED_PORT |
124+
Request::HEADER_X_FORWARDED_PROTO
125+
);
126+
})
127+
```
128+
129+
::tip
130+
For Cloudflare specifically, Laravel can use the `CF-Connecting-IP` header. The `HEADER_X_FORWARDED_FOR` setting works because Cloudflare also sets this header.
131+
::
132+
133+
:u-button{to="https://laravel.com/docs/requests#trusting-proxies" target="_blank" label="Learn more about Laravel's TrustProxies middleware" aria-label="Learn more about Laravel's TrustProxies middleware" size="md" color="primary" variant="outline" trailing-icon="i-lucide-arrow-right" class="font-bold ring ring-inset ring-blue-600 text-blue-600 hover:ring-blue-500 hover:text-blue-500"}
134+
135+
## How It Works
136+
137+
### Cloudflare
138+
When `TRUSTED_PROXY=cloudflare`:
139+
- Trusts Cloudflare's published IPv4 and IPv6 ranges
140+
- Uses the `CF-Connecting-IP` header to get the real client IP
141+
- Includes Docker network ranges for container-to-container communication
142+
143+
### Sucuri
144+
When `TRUSTED_PROXY=sucuri`:
145+
- Trusts Sucuri's WAF IP ranges
146+
- Uses the `X-Forwarded-For` header to get the real client IP
147+
- Includes Docker network ranges for container-to-container communication
148+
149+
### Local
150+
When `TRUSTED_PROXY=local`:
151+
- Trusts only private network ranges (RFC 1918)
152+
- Trusts localhost and IPv6 loopback addresses
153+
- Uses the `X-Forwarded-For` header
154+
- Perfect for setups with your own reverse proxy on the same network
155+
156+
## Security Considerations
157+
158+
::caution
159+
Incorrectly configuring trusted proxies can allow attackers to spoof their IP address by sending fake headers. Only trust IP ranges that you know are legitimate proxies.
160+
::
161+
162+
- **Don't trust all IPs**: Never set your web server to trust `X-Forwarded-For` from any IP address
163+
- **Keep configurations updated**: CDN IP ranges can change over time. Our images are updated regularly, but if security is critical, verify the ranges match your provider's published list
164+
- **Use the right option**: If you're not using Cloudflare or Sucuri, don't use those options. Use `local` if you have your own reverse proxy, or `off` if you don't need proxy trust
165+
166+
## Troubleshooting
167+
168+
### Still seeing proxy IP instead of real IP
169+
170+
1. **Check your CDN/proxy is in the trusted list**: Verify your proxy's IP is within the trusted ranges for your chosen option
171+
2. **Check the header being used**: Different proxies use different headers. Cloudflare uses `CF-Connecting-IP`, while most others use `X-Forwarded-For`
172+
3. **Check Laravel configuration**: Remember to also configure Laravel's `TrustProxies` middleware
173+
174+
### Application works but logs show wrong IP
175+
176+
Your web server might be configured correctly, but your application framework might need separate configuration. See the [Laravel Configuration](#laravel-configuration) section above.
177+
178+
### Need to trust a different proxy provider
179+
180+
If your proxy provider isn't listed, you can create a custom configuration. See [Custom Trusted Proxy Configuration](#custom-trusted-proxy-configuration) below.
181+
182+
## Custom Trusted Proxy Configuration
183+
184+
If you're using a proxy provider that isn't included (like AWS ALB, Fastly, or a custom load balancer), you can create your own trusted proxy configuration.
185+
186+
### Two Ways to Add Custom Configs
187+
188+
| Method | Best For | How It Works |
189+
|--------|----------|--------------|
190+
| **Dockerfile (recommended)** | Production deployments | Bakes the config into your image |
191+
| **Volume mount** | Local development | Mount the file at runtime |
192+
193+
The examples below use the Dockerfile approach. To use a volume mount instead, simply replace the `COPY` instruction with a volume mount in your `compose.yml`:
194+
195+
```yml [compose.yml]
196+
volumes:
197+
# Mount syntax: ./local/path:/container/path:ro
198+
- ./docker/trusted-proxy/aws-alb.conf:/etc/nginx/trusted-proxy/aws-alb.conf:ro
199+
```
200+
201+
| Variation | Config Path |
202+
|-----------|-------------|
203+
| `fpm-nginx` | `/etc/nginx/trusted-proxy/{name}.conf` |
204+
| `fpm-apache` | `/etc/apache2/trusted-proxy/{name}.conf` |
205+
| `frankenphp` | `/etc/frankenphp/trusted-proxy/{name}.caddyfile` |
206+
207+
::tip
208+
Always include Docker's internal network ranges (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`) so container-to-container communication works correctly.
209+
::
210+
211+
::warning
212+
Keep your custom IP ranges up to date. Cloud providers periodically update their IP ranges.
213+
::
214+
215+
### FPM-NGINX Custom Configuration
216+
217+
::code-tree{defaultValue="Dockerfile"}
218+
219+
```dockerfile [Dockerfile]
220+
FROM serversideup/php:8.5-fpm-nginx
221+
222+
# Copy our custom trusted proxy configuration
223+
COPY --chmod=644 docker/trusted-proxy/aws-alb.conf /etc/nginx/trusted-proxy/aws-alb.conf
224+
```
225+
226+
```yml [compose.yml]
227+
services:
228+
php:
229+
build:
230+
context: .
231+
dockerfile: Dockerfile
232+
ports:
233+
- "80:8080"
234+
volumes:
235+
- .:/var/www/html
236+
environment:
237+
TRUSTED_PROXY: "aws-alb"
238+
```
239+
240+
```conf [docker/trusted-proxy/aws-alb.conf]
241+
##
242+
# AWS ALB - Trusted Proxy (example)
243+
##
244+
245+
# Configure docker networks and loopback addresses
246+
set_real_ip_from 10.0.0.0/8;
247+
set_real_ip_from 172.16.0.0/12;
248+
set_real_ip_from 192.168.0.0/16;
249+
set_real_ip_from 127.0.0.1/8;
250+
set_real_ip_from ::1;
251+
set_real_ip_from fd00::/8;
252+
253+
# Add your proxy provider's IP ranges
254+
# Example: AWS us-east-1 ranges (check AWS docs for current IPs)
255+
set_real_ip_from 3.0.0.0/8;
256+
257+
# Set the header your proxy uses
258+
real_ip_header X-Forwarded-For;
259+
```
260+
::
261+
262+
### FPM-Apache Custom Configuration
263+
264+
::code-tree{defaultValue="Dockerfile"}
265+
266+
```dockerfile [Dockerfile]
267+
FROM serversideup/php:8.5-fpm-apache
268+
269+
# Copy our custom trusted proxy configuration
270+
COPY --chmod=644 docker/trusted-proxy/aws-alb.conf /etc/apache2/trusted-proxy/aws-alb.conf
271+
```
272+
273+
```yml [compose.yml]
274+
services:
275+
php:
276+
build:
277+
context: .
278+
dockerfile: Dockerfile
279+
ports:
280+
- "80:8080"
281+
volumes:
282+
- .:/var/www/html
283+
environment:
284+
TRUSTED_PROXY: "aws-alb"
285+
```
286+
287+
```conf [docker/trusted-proxy/aws-alb.conf]
288+
##
289+
# AWS ALB - Trusted Proxy (example)
290+
##
291+
292+
# Set the header your proxy uses
293+
RemoteIPHeader X-Forwarded-For
294+
295+
# Configure docker networks
296+
RemoteIPTrustedProxy 10.0.0.0/8
297+
RemoteIPTrustedProxy 172.16.0.0/12
298+
RemoteIPTrustedProxy 192.168.0.0/16
299+
RemoteIPTrustedProxy 127.0.0.1/8
300+
RemoteIPTrustedProxy ::1
301+
RemoteIPTrustedProxy fd00::/8
302+
303+
# Add your proxy provider's IP ranges
304+
# Example: AWS us-east-1 ranges (check AWS docs for current IPs)
305+
RemoteIPTrustedProxy 3.0.0.0/8
306+
```
307+
::
308+
309+
### FrankenPHP Custom Configuration
310+
311+
::code-tree{defaultValue="Dockerfile"}
312+
313+
```dockerfile [Dockerfile]
314+
FROM serversideup/php:8.5-frankenphp
315+
316+
# Copy our custom trusted proxy configuration
317+
COPY --chmod=644 docker/trusted-proxy/aws-alb.caddyfile /etc/frankenphp/trusted-proxy/aws-alb.caddyfile
318+
```
319+
320+
```yml [compose.yml]
321+
services:
322+
php:
323+
build:
324+
context: .
325+
dockerfile: Dockerfile
326+
ports:
327+
- "80:8080"
328+
volumes:
329+
- .:/var/www/html
330+
environment:
331+
TRUSTED_PROXY: "aws-alb"
332+
```
333+
334+
```caddyfile [docker/trusted-proxy/aws-alb.caddyfile]
335+
servers {
336+
# Trust Docker/private networks + loopback
337+
trusted_proxies static \
338+
10.0.0.0/8 \
339+
172.16.0.0/12 \
340+
192.168.0.0/16 \
341+
127.0.0.1/8 \
342+
::1 \
343+
fd00::/8 \
344+
3.0.0.0/8
345+
346+
# Set the header your proxy uses
347+
client_ip_headers X-Forwarded-For
348+
}
349+
```
350+
::

docs/content/docs/8.reference/1.environment-variable-specification.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,3 +119,4 @@ Setting environment variables all depends on what method you're using to run you
119119
`SSL_CERTIFICATE_FILE`<br />*Default: "/etc/ssl/private/self-signed-web.crt"*|Path to public certificate file for HTTPS. You must provide this file otherwise a self-signed key pair will be generated for you.|fpm-nginx,<br />fpm-apache
120120
`SSL_MODE`<br />*Default: "off"*|Configure how you would like to handle SSL. This can be "off" (HTTP only), "mixed" (HTTP + HTTPS), or "full" (HTTPS only). If you use HTTP, you may need to also change `PHP_SESSION_COOKIE_SECURE`.|fpm-nginx,<br />fpm-apache,<br />frankenphp
121121
`SSL_PRIVATE_KEY_FILE`<br />*Default: "/etc/ssl/private/self-signed-web.key"*|Path to private key file for HTTPS. You must provide this file otherwise a self-signed key pair will be generated for you.|fpm-nginx,<br />fpm-apache, <br />frankenphp
122+
`TRUSTED_PROXY`<br />*Default: "cloudflare"*|Configure which proxy IPs are trusted to pass the real client IP address. Valid options: `cloudflare`, `sucuri`, `local`, or `off`. See [Configuring Trusted Proxies](/docs/guide/configuring-trusted-proxies) for details.|fpm-nginx,<br />fpm-apache, <br />frankenphp

docs/content/index.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -105,10 +105,10 @@ These images [give a lot more]{.text-pink-500} than other PHP Docker Images.
105105
orientation: vertical
106106
---
107107
#title
108-
Native CloudFlare Support
108+
Trusted Proxy Support
109109

110110
#description
111-
Get real IP addresses from visitors from trusted proxies.
111+
Get real IP addresses from Cloudflare, Sucuri, or local proxies.
112112
:::
113113

114114
:::u-page-card

src/s6/etc/entrypoint.d/10-init-webserver-config.sh

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,8 @@ process_template() {
4242
return 1
4343
fi
4444

45-
# Get all environment variables starting with 'NGINX_', 'SSL_', `LOG_`, 'APACHE_', and 'HEALTHCHECK_PATH'
46-
subst_vars=$(env | grep -E '^(PHP_|NGINX_|SSL_|LOG_|APACHE_|HEALTHCHECK_PATH)' | cut -d= -f1 | awk '{printf "${%s},",$1}' | sed 's/,$//')
45+
# Get all environment variables starting with certain prefixes
46+
subst_vars=$(env | grep -E '^(PHP_|NGINX_|SSL_|LOG_|APACHE_|HEALTHCHECK_PATH|TRUSTED_PROXY)' | cut -d= -f1 | awk '{printf "${%s},",$1}' | sed 's/,$//')
4747

4848
# Validate that all required variables are set
4949
for var_name in $(echo "$subst_vars" | tr ',' ' '); do

src/variations/fpm-apache/Dockerfile

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,8 @@ ENV APACHE_DOCUMENT_ROOT=/var/www/html/public \
9999
SHOW_WELCOME_MESSAGE=true \
100100
SSL_MODE=off \
101101
SSL_CERTIFICATE_FILE=/etc/ssl/private/self-signed-web.crt \
102-
SSL_PRIVATE_KEY_FILE=/etc/ssl/private/self-signed-web.key
102+
SSL_PRIVATE_KEY_FILE=/etc/ssl/private/self-signed-web.key \
103+
TRUSTED_PROXY=cloudflare
103104

104105
# copy our scripts
105106
COPY --chmod=755 src/common/ /

0 commit comments

Comments
 (0)