Forward CF-Connecting-IP to Caddy

1. Output of caddy version:

v2.5.2 h1:eCJdLyEyAGzuQTa5Mh3gETnYWDClo1LjtQm2q9RNZrs=

2. How I run Caddy:

Caddy downloaded with the dns-cloudflare add on from the official website

a. System environment:

Ubuntu 22.04, running Caddy with systemd

b. Command:

sudo service caddy start

c. Service/unit/compose file:

It’s the default file

d. My complete Caddy config:

{
	admin off
	acme_dns cloudflare <cloudflare_api_token_here>
}
(errors) {
	handle_errors {
		root * /var/www/internal/errors
		rewrite * /{http.error.status_code}.html
		file_server
	}
}
(logging) {
	log {
		output file /var/log/caddy/caddy.log {
			roll_size 15mb
			roll_keep 20
		}
	}
}
(php) {
	php_fastcgi unix//run/php/php8.1-fpm.sock
}
www.telesphoreo.me {
	import logging
	redir https://telesphoreo.me{uri}
}
telesphoreo.me {
	import logging
	import errors
	import php
	root * /var/www/telesphoreo.me
	file_server browse
	encode gzip zstd
	@denied path /assets/ /old_html/* /new_html/* /recyclebin/* /nitrogen/ /nexus/ /wave/
	error @denied 403
}
blog.telesphoreo.me {
	import logging
	import php
	root * /var/www/blog.telesphoreo.me
	file_server
	encode gzip
}
db2.telesphoreo.me {
	import logging
	import php
	root * /usr/share/phpmyadmin
	file_server
}
git.telesphoreo.me {
	import logging
	reverse_proxy http://localhost:3000
}
nexus.telesphoreo.me {
	import logging
	reverse_proxy http://localhost:8082
}
docker.telesphoreo.me {
	import logging
	reverse_proxy http://localhost:18444
}
panel.telesphoreo.me {
	import logging
	import php
	root * /var/www/pterodactyl/public
	file_server
	header X-Content-Type-Options nosniff
	header X-XSS-Protection "1; mode=block"
	header X-Robots-Tag none
	header Content-Security-Policy "frame-ancestors 'self'"
	header X-Frame-Options DENY
	header Referrer-Policy same-origin
	request_body {
		max_size 100m
	}
	respond /.ht* 403
}
pictochat.telesphoreo.me {
	import logging
	reverse_proxy http://localhost:8080
}
wordle.telesphoreo.me {
	import logging
	root * /var/www/wordle.telesphoreo.me/games/wordle
	file_server
	encode gzip
}
ci.plex.us.org {
	import logging
	reverse_proxy http://localhost:8081 {
		trusted_proxies 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
	}
}
discord.plex.us.org {
	import logging
	redir https://discord.gg/MzjRjFAcrZ
}
forum.plex.us.org {
	import logging
	redir https://plex.us.org{uri}
}
git.plex.us.org {
	import logging
	redir https://git.telesphoreo.me{uri}
}
httpd.plex.us.org {
	import logging
	reverse_proxy 172.18.0.1:27192
}
plan.plex.us.org {
	import logging
	reverse_proxy 172.18.0.1:8804
}
staging-docs.plex.us.org {
	import logging
	root * /var/www/plexus.org/build
	file_server
}
plex.us.org {
	import logging
	import php
	try_files {path} /index.php
	root * /var/www/xenforo
	file_server
	respond /.ht* 403
}
scissors.gg {
	import logging
	import errors
	handle /javadoc* {
		root * /var/www/scissors.gg/build
		file_server
	}
	handle {
		root * /var/www/scissors.gg/build
		try_files {path} /index.html
		file_server
	}
}
www.scissors.gg {
	import logging
	redir https://scissors.gg{uri}
}
vw.skyline.to {
	import logging
	encode gzip
	reverse_proxy /notifications/hub/negotiate localhost:6120
	reverse_proxy /notifications/hub localhost:3012
	reverse_proxy localhost:6120 {
		header_up X-Real-IP {>CF-Connecting-IP}
	}
	header {
		Strict-Transport-Security "max-age=31536000;"
		X-XSS-Protection "1; mode=block"
		X-Frame-Options "DENY"
		X-Robots-Tag "none"
		-Server
	}
}

3. The problem I’m having:

Basically, I am reverse proxying a Vaultwarden instance (vw.skyline.to). The problem is, it’s behind a Cloudflare proxy and the IP addresses come from Cloudflare. This is a well known and documented problem, but there is a CF-Connecting-IP header passed which does have the users IP address. Unfortunately, I’m not finding a way to send this. In Vaultwarden, if I set the header as CF-Connecting-IP, it says no match (i.e. Caddy is not forwarding it at all). I can use X-Forwarded-To just fine, or add a X-Real-IP header to the Caddyfile (which is the Vaultwarden default). I can find very little on how to tell Caddy to use CF-Connecting-IP as the IP of the visitor. I found this ancient thread from Caddy v1 (Cloudflare: Can Caddy restore the real visitors IP address somehow? (CF-Connecting-IP)), but it appears that this does not work anymore. I used the syntax from Caddy v1, but instead of header_upstream used header_up. It still was using the Cloudflare proxies IP. I’d also like to know if there’s a way to have it apply globally, instead of just to one reverse proxy.

4. Error messages and/or full log output:

There are no error messages

5. What I already tried:

I thought that setting up the Cloudflare DNS extension for Caddy was the solution, but apparently not. Also, research from previous threads. There was something called “realip” in Caddy v1, but all of the docs now return a 404.

6. Links to relevant resources:

Would this work ?

header X-Real-IP {http.request.header.CF-Connecting-IP}

or

header X-Forwarded-For {http.request.header.CF-Connecting-IP}

1 Like

Follow the instructions below to set up Cloudflare rules to have it securely set X-Forwarded-For so that Caddy can use it. Then configure trusted_proxies in your reverse_proxy (or php_fastcgi) to trust the incoming X-Forwarded-* headers when they come from Cloudflare’s IP ranges.

2 Likes

(I think) both of your solutions combined was the correct answer.
I now have this block:

(trusted_proxy_list) {
	trusted_proxies 10.0.0.0/8 172.16.0.0/16 192.168.0.0/16 fc00::/7 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
	header_up X-Forwarded-For {http.request.header.CF-Connecting-IP}
}

and I import it on all the reverse_proxy’s that I have and it does work. I setup a Cloudflare rule for the Request Header as follows:

I haven’t tested this against spoofing attacks but it does appear that I’m getting the correct IP in Vaultwarden now.

I’m not sure how to replicate this in a non-reverse proxy sense though. I added request_header X-Forwarded-For {http.request.header.CF-Connecting-IP} to the telesphoreo.me block (and https://telesphoreo.me/ip.php returns your IP) and it’s still a Cloudflare one. Code for reference:

<?php
$host = gethostbyaddr($_SERVER["REMOTE_ADDR"]);
echo $host;
?>

Edit: I tried setting it for the php_fastcgi as follows:

(trusted_proxy_list) {
	trusted_proxies 10.0.0.0/8 172.16.0.0/16 192.168.0.0/16 fc00::/7 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
        header_up X-Forwarded-For {http.request.header.CF-Connecting-IP}
}
(php) {
	php_fastcgi unix//run/php/php8.1-fpm.sock {
		import trusted_proxy_list
	}
}

and still no dice

Yeah this will always return the IP address of the proxy (or more precisely, the closest direct client). The REMOTE_ADDR contains the source IP address on the TCP packets.

You have to write code in your application to read from the X-Forwarded-For header to get the real client’s IP address.

Well, that’s unfortunate I guess, but also probably the solution. Unfortunately most of the software isn’t written by me so I can’t change it easily. Just tested spoofing the X-Forwarded-For header and it kept my actual IP so there’s that.

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