[GH-ISSUE #341] How to set real ip header to CF-Connecting-IP for cloudflare proxy. #3343

Closed
opened 2026-04-20 07:16:27 -05:00 by GiteaMirror · 17 comments
Owner

Originally created by @jbarozi-psl on GitHub (Mar 16, 2025).
Original GitHub issue: https://github.com/fosrl/pangolin/issues/341

I have enabled the cloudflare proxy on the domain side but need a way to set the real ip header to CF-Connection-IP. I do not see a way to set this correctly.

I need this to enable rules on some applications i have behind pangolin.

Originally created by @jbarozi-psl on GitHub (Mar 16, 2025). Original GitHub issue: https://github.com/fosrl/pangolin/issues/341 I have enabled the cloudflare proxy on the domain side but need a way to set the real ip header to CF-Connection-IP. I do not see a way to set this correctly. I need this to enable rules on some applications i have behind pangolin.
GiteaMirror added the good first issueenhancement labels 2026-04-20 07:16:27 -05:00
Author
Owner

@CooperFLe commented on GitHub (Mar 17, 2025):

You should be able to achieve this using traefik plugins. There are numerous for CF proxies. Here is just one example
https://plugins.traefik.io/plugins/62e97498e2bf06d4675b9443/real-ip-from-cloudflare-proxy-tunnel

<!-- gh-comment-id:2730124368 --> @CooperFLe commented on GitHub (Mar 17, 2025): You should be able to achieve this using traefik plugins. There are numerous for CF proxies. Here is just one example https://plugins.traefik.io/plugins/62e97498e2bf06d4675b9443/real-ip-from-cloudflare-proxy-tunnel
Author
Owner

@miloschwartz commented on GitHub (Mar 17, 2025):

Hey, we've been aware of the special header that CF sets with the original client's IP. This partly from a lack of understanding on our end, but wouldn't this header be able to be spoofed? Someone could send a different IP as the header to bypass a restriction. Let me know what you think.

<!-- gh-comment-id:2730174720 --> @miloschwartz commented on GitHub (Mar 17, 2025): Hey, we've been aware of the special header that CF sets with the original client's IP. This partly from a lack of understanding on our end, but wouldn't this header be able to be spoofed? Someone could send a different IP as the header to bypass a restriction. Let me know what you think.
Author
Owner

@CooperFLe commented on GitHub (Mar 17, 2025):

The client isn't setting the header. It is coming from the middleware, so if the client sets it, the middleware should overwrite it. However, that depends on the implementation of the middleware.

<!-- gh-comment-id:2730432595 --> @CooperFLe commented on GitHub (Mar 17, 2025): The client isn't setting the header. It is coming from the middleware, so if the client sets it, the middleware **should** overwrite it. However, that depends on the implementation of the middleware.
Author
Owner

@notmeta commented on GitHub (Apr 8, 2025):

Hey, we've been aware of the special header that CF sets with the original client's IP. This partly from a lack of understanding on our end, but wouldn't this header be able to be spoofed? Someone could send a different IP as the header to bypass a restriction. Let me know what you think.

Cloudflare publishes their IPs which you can use to verify the request came from CF and whether or not to trust the header: https://www.cloudflare.com/ips/

<!-- gh-comment-id:2787697962 --> @notmeta commented on GitHub (Apr 8, 2025): > Hey, we've been aware of the special header that CF sets with the original client's IP. This partly from a lack of understanding on our end, but wouldn't this header be able to be spoofed? Someone could send a different IP as the header to bypass a restriction. Let me know what you think. Cloudflare publishes their IPs which you can use to verify the request came from CF and whether or not to trust the header: https://www.cloudflare.com/ips/
Author
Owner

@gitmotion commented on GitHub (May 12, 2025):

You should be able to achieve this using traefik plugins. There are numerous for CF proxies. Here is just one example https://plugins.traefik.io/plugins/62e97498e2bf06d4675b9443/real-ip-from-cloudflare-proxy-tunnel

any luck with this? i set up this plugin and used it on the traefik config but can't seem to get the ip rules working on pangolin.
it's not the biggest deal since im able to block via cf waf rules/etc but would be nice to see if anyone has this passing/working correctly

Edit:

  • not sure if this is accurate but after adding the plugin it looks like my auth provider is able to capture the real ip?
  • however still can't block with pangolin for whatever reason. guessing it has something to do with badger?
  • see below: New York entries coming from real ip VS. Cloudflare IPs below it
    Image
<!-- gh-comment-id:2874526973 --> @gitmotion commented on GitHub (May 12, 2025): > You should be able to achieve this using traefik plugins. There are numerous for CF proxies. Here is just one example https://plugins.traefik.io/plugins/62e97498e2bf06d4675b9443/real-ip-from-cloudflare-proxy-tunnel any luck with this? i set up this plugin and used it on the traefik config but can't seem to get the ip rules working on pangolin. it's not the biggest deal since im able to block via cf waf rules/etc but would be nice to see if anyone has this passing/working correctly Edit: - not sure if this is accurate but after adding the plugin it looks like my auth provider is able to capture the real ip? - however still can't block with pangolin for whatever reason. guessing it has something to do with badger? - see below: New York entries coming from real ip VS. Cloudflare IPs below it ![Image](https://github.com/user-attachments/assets/30d52796-640f-4aa2-9659-950342cd597c)
Author
Owner

@gitmotion commented on GitHub (May 13, 2025):

Hey, we've been aware of the special header that CF sets with the original client's IP. This partly from a lack of understanding on our end, but wouldn't this header be able to be spoofed? Someone could send a different IP as the header to bypass a restriction. Let me know what you think.

hey there!

just wanted to add some more findings onto here. i may be wrong but it seems like, if not using cloudflare/not parsing cf headers this is more prone to spoofing?

tldr:
pangolin can't parse cf headers = spoofable with cf workers (current state)
pangolin can parse cf headers = can block ips / cf workers (desired state)

seems like CF-Connecting-IP would be the authoritative truth when proxying through CF

on the contrary however, i also saw that someone can use a cloudflare worker to replace x-real-ip with some caveats

  • https://developers.cloudflare.com/fundamentals/reference/http-headers/#cf-connecting-ip-in-worker-subrequests
  • In same-zone Worker subrequests, the value of CF-Connecting-IP reflects the value of x-real-ip (the client's IP). x-real-ip can be altered by the user in their Worker script. In cross-zone subrequests from one Cloudflare zone to another Cloudflare zone, the CF-Connecting-IP value will be set to the Worker client IP address 2a06:98c0:3600::103 for security reasons. For Worker subrequests destined for a non-Cloudflare customer zone, the CF-Connecting-IP and x-real-ip headers will both reflect the client's IP address, with only the x-real-ip header able to be altered. When no Worker subrequest is triggered, cf-connecting-ip reflects the client's IP address and the x-real-ip header is stripped.

  • interestingly i've seen this ip on my cf waf events 2a06:98c0:3600::103 looking for wordpress exploits, which means they must be using a cf worker. but in either case, being able to pass/parse the CF-Connecting-IP would at least allow CF users to explicitly ban this per resources' ip rules within pangolin
  • Image

where as x-forwarded-for would have the ips appended if already set

  • https://developers.cloudflare.com/fundamentals/reference/http-headers/#x-forwarded-for
  • If, on the other hand, an X-Forwarded-For header was already present in the request to Cloudflare, Cloudflare will append the IP address of the HTTP proxy connecting to Cloudflare to the header. For example, if the original visitor IP address is 203.0.113.1 and a request is proxied through two proxies: proxy A with an IP address of 198.51.100.101 and proxy B with an IP address of 198.51.100.102 before being proxied to Cloudflare, then Cloudflare will send X-Forwarded-For: 203.0.113.1,198.51.100.101,198.51.100.102 to the origin.

i'm sure you looked into this a lot already but in theory it seems like it should be possible to allow users to define a CF_IP_LIST (making it the user's responsibility to keep this up-to-date) or have a setting where CF_PROXY=true

  • then if pangolin/badger comes across one of those ips on the list it knows to check the CF-Connecting-IP instead.

from what it seems like, having the ability to check CF-Connecting-IP is more secure than not being able to

<!-- gh-comment-id:2878163497 --> @gitmotion commented on GitHub (May 13, 2025): > Hey, we've been aware of the special header that CF sets with the original client's IP. This partly from a lack of understanding on our end, but wouldn't this header be able to be spoofed? Someone could send a different IP as the header to bypass a restriction. Let me know what you think. hey there! just wanted to add some more findings onto here. i may be wrong but it seems like, if not using cloudflare/not parsing cf headers this is more prone to spoofing? tldr: pangolin **can't** parse cf headers = spoofable with cf workers (current state) pangolin **can** parse cf headers = can block ips / cf workers (desired state) seems like `CF-Connecting-IP` would be the authoritative truth when proxying through CF - https://developers.cloudflare.com/fundamentals/reference/http-headers/#cf-connecting-ip - >CF-Connecting-IP provides the client IP address connecting to Cloudflare to the origin web server. This header will only be sent on the traffic from Cloudflare's edge to your origin web server. on the contrary however, i also saw that someone can use a cloudflare worker to replace x-real-ip with some caveats - https://developers.cloudflare.com/fundamentals/reference/http-headers/#cf-connecting-ip-in-worker-subrequests - > In same-zone Worker subrequests, the value of CF-Connecting-IP reflects the value of x-real-ip (the client's IP). x-real-ip can be altered by the user in their Worker script. In cross-zone subrequests from one Cloudflare zone to another Cloudflare zone, the CF-Connecting-IP value will be set to the Worker client IP address `2a06:98c0:3600::103` for security reasons. For Worker subrequests destined for a non-Cloudflare customer zone, the CF-Connecting-IP and x-real-ip headers will both reflect the client's IP address, with only the x-real-ip header able to be altered. When no Worker subrequest is triggered, cf-connecting-ip reflects the client's IP address and the x-real-ip header is stripped. - interestingly i've seen this ip on my cf waf events `2a06:98c0:3600::103` looking for wordpress exploits, which means they must be using a cf worker. but in either case, being able to pass/parse the `CF-Connecting-IP` would at least allow CF users to explicitly ban this per resources' ip rules within pangolin - ![Image](https://github.com/user-attachments/assets/69678b98-89f1-49d9-821d-429c8757878d) where as `x-forwarded-for` would have the ips appended if already set - https://developers.cloudflare.com/fundamentals/reference/http-headers/#x-forwarded-for - >If, on the other hand, an X-Forwarded-For header was already present in the request to Cloudflare, Cloudflare will append the IP address of the HTTP proxy connecting to Cloudflare to the header. For example, if the original visitor IP address is 203.0.113.1 and a request is proxied through two proxies: proxy A with an IP address of 198.51.100.101 and proxy B with an IP address of 198.51.100.102 before being proxied to Cloudflare, then Cloudflare will send X-Forwarded-For: 203.0.113.1,198.51.100.101,198.51.100.102 to the origin. i'm sure you looked into this a lot already but in theory it seems like it should be possible to allow users to define a `CF_IP_LIST` (making it the user's responsibility to keep this up-to-date) or have a setting where `CF_PROXY`=true - then if pangolin/badger comes across one of those ips on the list it knows to check the `CF-Connecting-IP` instead. from what it seems like, having the ability to check `CF-Connecting-IP` is more secure than not being able to
Author
Owner

@overclockit commented on GitHub (May 16, 2025):

As I mentioned in GitHub Issue #619, Badger's current implementation handles IP validation relying solely on &req.RemoteAddr to determine client IP and whether an IP matches a rule, which when using a proxy, whether Cloudflare/Orange Cloud or other will always return the address of the proxy or service creating/forwarding the HTTP request, like a CF worker.

I have this working in my setup by using a locally edited version of Badger but any permanent fix should come with validation that trustedIPs is set and almost as importantly not wide open / set to 0.0.0.0/0. So long as trustedIPs is already set within Traefik using Cloudflare CIDR's, Badger doesn't need to revalidate the header as it is assumed trusted by Traefik's own rules.

<!-- gh-comment-id:2885726753 --> @overclockit commented on GitHub (May 16, 2025): As I mentioned in GitHub Issue [#619](https://github.com/fosrl/pangolin/issues/619#issuecomment-2881322127), Badger's current implementation handles IP validation relying solely on &req.RemoteAddr to determine client IP and whether an IP matches a rule, which when using a proxy, whether Cloudflare/Orange Cloud or other will always return the address of the proxy or service creating/forwarding the HTTP request, like a CF worker. I have this working in my setup by using a locally edited version of Badger but any permanent fix should come with validation that trustedIPs is set and almost as importantly not wide open / set to 0.0.0.0/0. So long as trustedIPs is already set within Traefik using Cloudflare CIDR's, Badger doesn't need to revalidate the header as it is assumed trusted by Traefik's own rules.
Author
Owner

@miloschwartz commented on GitHub (May 19, 2025):

@OverclockIT Understood this makes sense. If anyone has the capacity to open a PR to begin work on this that would be a huge help, but we want to get to this.

<!-- gh-comment-id:2892061216 --> @miloschwartz commented on GitHub (May 19, 2025): @OverclockIT Understood this makes sense. If anyone has the capacity to open a PR to begin work on this that would be a huge help, but we want to get to this.
Author
Owner

@Makifun commented on GitHub (Nov 24, 2025):

As I mentioned in GitHub Issue #619, Badger's current implementation handles IP validation relying solely on &req.RemoteAddr to determine client IP and whether an IP matches a rule, which when using a proxy, whether Cloudflare/Orange Cloud or other will always return the address of the proxy or service creating/forwarding the HTTP request, like a CF worker.

I have this working in my setup by using a locally edited version of Badger but any permanent fix should come with validation that trustedIPs is set and almost as importantly not wide open / set to 0.0.0.0/0. So long as trustedIPs is already set within Traefik using Cloudflare CIDR's, Badger doesn't need to revalidate the header as it is assumed trusted by Traefik's own rules.

Any way you could share your edits? I would also like to be able to see my visitors real IPs and not the cloudflare proxy... 🥺 I tried using your fork(and your dev branch) but I was not successful(using localPlugins in Traefik).

<!-- gh-comment-id:3571459853 --> @Makifun commented on GitHub (Nov 24, 2025): > As I mentioned in GitHub Issue [#619](https://github.com/fosrl/pangolin/issues/619#issuecomment-2881322127), Badger's current implementation handles IP validation relying solely on &req.RemoteAddr to determine client IP and whether an IP matches a rule, which when using a proxy, whether Cloudflare/Orange Cloud or other will always return the address of the proxy or service creating/forwarding the HTTP request, like a CF worker. > > I have this working in my setup by using a locally edited version of Badger but any permanent fix should come with validation that trustedIPs is set and almost as importantly not wide open / set to 0.0.0.0/0. So long as trustedIPs is already set within Traefik using Cloudflare CIDR's, Badger doesn't need to revalidate the header as it is assumed trusted by Traefik's own rules. Any way you could share your edits? I would also like to be able to see my visitors real IPs and not the cloudflare proxy... 🥺 I tried using your fork(and your dev branch) but I was not successful(using localPlugins in Traefik).
Author
Owner

@HiDef888 commented on GitHub (Dec 18, 2025):

https://docs.pangolin.net/self-host/advanced/cloudflare-proxy#getting-the-real-client-ip

I have been unsuccessful in getting this to work. It seems like some information is missing from the documentation or at least it could be a bit more specific.

<!-- gh-comment-id:3672765281 --> @HiDef888 commented on GitHub (Dec 18, 2025): [https://docs.pangolin.net/self-host/advanced/cloudflare-proxy#getting-the-real-client-ip](url) I have been unsuccessful in getting this to work. It seems like some information is missing from the documentation or at least it could be a bit more specific.
Author
Owner

@gael1980 commented on GitHub (Dec 19, 2025):

Same problem

<!-- gh-comment-id:3674023300 --> @gael1980 commented on GitHub (Dec 19, 2025): Same problem
Author
Owner

@jghaanstra commented on GitHub (Dec 19, 2025):

@HiDef888 @gael1980 the issue is caused by Badger. There is a patched Badger plugin you can use to get this working properly although this solution is flawed in it's own way. Read more about it here.

https://github.com/fosrl/badger/issues/6

We need to wait for the guys from Pangolin to fix this properly by updating Badger with a more bulletproof solution.

<!-- gh-comment-id:3674037337 --> @jghaanstra commented on GitHub (Dec 19, 2025): @HiDef888 @gael1980 the issue is caused by Badger. There is a patched Badger plugin you can use to get this working properly although this solution is flawed in it's own way. Read more about it here. https://github.com/fosrl/badger/issues/6 We need to wait for the guys from Pangolin to fix this properly by updating Badger with a more bulletproof solution.
Author
Owner

@gael1980 commented on GitHub (Dec 19, 2025):

Thanks for your reply.

<!-- gh-comment-id:3674101470 --> @gael1980 commented on GitHub (Dec 19, 2025): Thanks for your reply.
Author
Owner

@gitmotion commented on GitHub (Dec 19, 2025):

hey all,

been keeping up with the threads about this and i totally understand and agree with the need of checking against a cloudflare ip list before accepting the CF-Connecting-IP header and have been pondering about this / just want to confirm:

in reference to @overclockit's comment here:

I have this working in my setup by using a locally edited version of Badger but any permanent fix should come with validation that trustedIPs is set and almost as importantly not wide open / set to 0.0.0.0/0. So long as trustedIPs is already set within Traefik using Cloudflare CIDR's, Badger doesn't need to revalidate the header as it is assumed trusted by Traefik's own rules.

  • let's you are proxying through cloudflare, only accepting traffic through cloudflare ips via traefik configs, and coupled with a badger plugin like @jghaanstra's
    • doesn't this mean the cf-connecting-ip header is not spoofable, since traefik/pangolin routes would only be accessible via cloudflare?
    • so for users proxying through cloudflare, with the correct traefik setup, would an implementation like this be acceptable/secure?
  • however, it's totally understood that it would be spoofable if not CF proxied - making the implementation not a viable solution across the board, since not all users are CF proxying.

thanks for any insight! 🙏🏻

<!-- gh-comment-id:3676611945 --> @gitmotion commented on GitHub (Dec 19, 2025): hey all, been keeping up with the threads about this and i totally understand and agree with the need of checking against a cloudflare ip list before accepting the `CF-Connecting-IP` header and have been pondering about this / just want to confirm: in reference to @overclockit's comment here: > I have this working in my setup by using a locally edited version of Badger but any permanent fix should come with validation that trustedIPs is set and almost as importantly not wide open / set to 0.0.0.0/0. So long as trustedIPs is already set within Traefik using Cloudflare CIDR's, Badger doesn't need to revalidate the header as it is assumed trusted by Traefik's own rules. - let's you are proxying through cloudflare, only accepting traffic through cloudflare ips via traefik configs, and coupled with a badger plugin like @jghaanstra's - doesn't this mean the `cf-connecting-ip` header is not spoofable, since traefik/pangolin routes would only be accessible via cloudflare? - **so for users proxying through cloudflare, with the correct traefik setup, would an implementation like this be acceptable/secure?** - however, it's totally understood that it would be spoofable if not CF proxied - making the implementation not a viable solution across the board, since not all users are CF proxying. thanks for any insight! 🙏🏻
Author
Owner

@onno204 commented on GitHub (Dec 25, 2025):

fyi:
Badger has recently released version 1.3.0 which should support the CF-Connecting-IP header by default.
https://github.com/fosrl/badger?tab=readme-ov-file#ip-handling-configuration

As I don't use Cloudflare I haven't tested this

<!-- gh-comment-id:3691816959 --> @onno204 commented on GitHub (Dec 25, 2025): fyi: Badger has recently released version 1.3.0 which should support the `CF-Connecting-IP` header by default. https://github.com/fosrl/badger?tab=readme-ov-file#ip-handling-configuration As I don't use Cloudflare I haven't tested this
Author
Owner

@CyberSparkCloud commented on GitHub (Dec 25, 2025):

fyi: Badger has recently released version 1.3.0 which should support the CF-Connecting-IP header by default. https://github.com/fosrl/badger?tab=readme-ov-file#ip-handling-configuration

As I don't use Cloudflare I haven't tested this

I've tested it with Cloudflare and it works perfectly now.

<!-- gh-comment-id:3691819799 --> @CyberSparkCloud commented on GitHub (Dec 25, 2025): > fyi: Badger has recently released version 1.3.0 which should support the `CF-Connecting-IP` header by default. https://github.com/fosrl/badger?tab=readme-ov-file#ip-handling-configuration > > As I don't use Cloudflare I haven't tested this I've tested it with Cloudflare and it works perfectly now.
Author
Owner

@gael1980 commented on GitHub (Dec 26, 2025):

Yes, I tested. It works without problems

<!-- gh-comment-id:3692458963 --> @gael1980 commented on GitHub (Dec 26, 2025): Yes, I tested. It works without problems
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/pangolin#3343