Cloudflare proxy does not show real client ip

1. The problem I’m having:

The client ipv4 ip is always the cloudflare proxy ip, ipv6 is the real client ip. So my php applications just sees the cloudflare ip (they prefer ipv4).

my real ip:

IPv4: 79.12.34.56
IPv6: b36e:1d3f:f79c:0bd3:8620:a7db:eca5:2a02

the REMOTE_ADDR is always showing the cloudflare ip.

2. Error messages and/or full log output:

/var/www/test.php

REMOTE_ADDR: 162.158.86.192
HTTP_X_FORWARDED_FOR: b36e:1d3f:f79c:0bd3:8620:a7db:eca5:2a02, 162.158.86.192
HTTP_CF_CONNECTING_IP: b36e:1d3f:f79c:0bd3:8620:a7db:eca5:2a02
HTTP_CLIENT_IP:
HTTP_X_REAL_IP:

/var/log/caddy/access.log

{
    "level": "info",
    "ts": 1739100999.885149,
    "logger": "http.log.access.log0",
    "msg": "handled request",
    "request": {
        "remote_ip": "172.71.164.196",
        "remote_port": "25810",
        "client_ip": "b36e:1d3f:f79c:0bd3:8620:a7db:eca5:2a02",
        "proto": "HTTP/2.0",
        "method": "GET",
        "host": "domain.ltd",
        "uri": "/test.php",
        "headers": {
            "X-Forwarded-For": [
                "b36e:1d3f:f79c:0bd3:8620:a7db:eca5:2a02"
            ],
            "Cf-Connecting-Ip": [
                "b36e:1d3f:f79c:0bd3:8620:a7db:eca5:2a02"
            ],
            "User-Agent": [
                "curl/8.10.1"
            ],
            "Cf-Ipcountry": [
                "DE"
            ],
            "Cdn-Loop": [
                "cloudflare; loops=1"
            ],
            "Cf-Visitor": [
                "{\"scheme\":\"https\"}"
            ],
            "Cf-Ray": [
                "90f39021192dd2b6-FRA"
            ],
            "Accept": [
                "*/*"
            ],
            "Accept-Encoding": [
                "gzip, br"
            ],
            "X-Forwarded-Proto": [
                "https"
            ]
        },
        "tls": {
            "resumed": false,
            "version": 772,
            "cipher_suite": 4865,
            "proto": "h2",
            "server_name": "domain.ltd"
        }
    },
    "bytes_read": 0,
    "user_id": "",
    "duration": 0.008822637,
    "size": 181,
    "status": 404,
    "resp_headers": {
        "Server": [
            "Caddy"
        ],
        "Vary": [
            "accept-language"
        ],
        "X-Frame-Options": [
            "SAMEORIGIN"
        ],
        "X-Content-Type-Options": [
            "nosniff"
        ],
        "Status": [
            "404 Not Found"
        ],
        "Content-Type": [
            "application/json"
        ],
        "Alt-Svc": [
            "h3=\":443\"; ma=2592000"
        ],
        "Cache-Control": [
            "private"
        ],
        "Set-Cookie": [
            "REDACTED"
        ]
    }
}

3. Caddy version:

v2.9.1

4. How I installed and ran Caddy:

Installed via caddy repo

apt install caddy

a. System environment:

Ubuntu 24.04, systemd

d. My complete Caddy config:

/etc/caddy/Caddyfile

{
  servers {
    trusted_proxies static 173.245.48.0/20 103.21.244.0/22 103.22.200.0/22 103.31.4.0/22 141.101.64.0/18 108.162.192.0/18 190.93.240.0/20 188.114.96.0/20 197.234.240.0/22 198.41.128.0/17 162.158.0.0/15 104.16.0.0/13 104.24.0.0/14 172.64.0.0/13 131.0.72.0/22 2400:cb00::/32 2606:4700::/32 2803:f800::/32 2405:b500::/32 2405:8100::/32 2a06:98c0::/29 2c0f:f248::/32
    client_ip_headers Cf-Connecting-Ip X-Forwarded-For
  }
}

domain.ltd {

  root * /var/www/
  encode zstd gzip
  file_server

  php_fastcgi unix//run/php/php-fpm.sock

}

It’s there. Your IPv6 is there and visible in your PHP app.

HTTP_X_FORWARDED_FOR: b36e:1d3f:f79c:0bd3:8620:a7db:eca5:2a02(myip), 162.158.86.192

like said the ipv6 is correct and the ipv4 (that get picked up by php) is still the cloudflare address

curl -ipv4 https://domain.tdl
REMOTE_ADDR: 172.71.164.31
HTTP_X_FORWARDED_FOR: 79.12.34.56(myip), 172.71.164.31
HTTP_CF_CONNECTING_IP: 79.12.34.56(myip)
HTTP_CLIENT_IP:
HTTP_X_REAL_IP:

curl https://domain.tdl
REMOTE_ADDR: 172.71.164.31
HTTP_X_FORWARDED_FOR: b36e:1d3f:f79c:0bd3:8620:a7db:eca5:2a02(myip), 172.71.164.31
HTTP_CF_CONNECTING_IP: b36e:1d3f:f79c:0bd3:8620:a7db:eca5:2a02(myip)
HTTP_CLIENT_IP:
HTTP_X_REAL_IP:

Shouldn’t REMOTE_ADDR be rewritten with the correct ip ?
or do I need to rewrite all php applications that they use the HTTP_CF_CONNECTING_IP instead of REMOTE_ADDR ?

No. Caddy is behaving correctly as expected. You should be picking up the left-most value of the X_FORWARDED_FOR, which contains the list of IP addresses/hosts (hops) that handed/forwarded the request.

How do I prevent faking the ips then? Afaik there is no way how to know what is the real ip from HTTP_X_FORWARDED_FOR.

curl https://domain.tdl
HTTP_X_FORWARDED_FOR: 79.12.34.56(myip), 172.69.109.69(cloudflare)
curl --header "X-Forwarded-For: 192.168.1.2" https://domain.tdl
HTTP_X_FORWARDED_FOR: 192.168.1.2(faked),79.12.34.56(myip), 172.69.109.69(cloudflare)

Can I overwrite HTTP_X_FORWARDED_FOR with HTTP_CF_CONNECTING_IP ?

This is Caddy’s job when you configure trusted_proxies.

Let Caddy do its job

If you look at the config above trusted_proxies are already configured, no idea if I made a mistake somewhere that it does not behave like expected ?

Caddy is behaving as expected as it includes your IP address in the X-Forwarded-For header when the request comes through the network defined in trusted_proxies. This is how fake IP addresses are prevented. This is what I meant by “let Caddy do its job”, meaning it’ll only accept that header if the request is coming through the IP address marked as trusted per your configuration.

The correct behavior is for REMOTE_ADDRESS to be Caddy’s address, and your application should be picking up the left-most value of the X-Forwarded-For. It’s wrong if Caddy had put your IP address in REMOTE_ADDRESS.

This is correct. It’s your applications responsibility to then trust the X-Forwarded-For header provided by Caddy and change the REMOTE_ADDR etc. within the environment as required. For example the Python Flask framework has this middleware that does just this.

1 Like

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.