[GH-ISSUE #905] Prefer Cloudflare IP header instead of X-Forwarded-For #639

Closed
opened 2026-05-07 00:26:10 +02:00 by BreizhHardware · 9 comments

Originally created by @ChokunPlayZ on GitHub (Oct 1, 2023).
Original GitHub issue: https://github.com/binwiederhier/ntfy/issues/905

💡 Idea
I think there's not many configs like mine where the setup involves multiple reverse proxy (in my case NGINX and Cloudflare)
the current "behind proxy" setting cause issue because
NGINX attach 2 IP in the forwarded for header, ntfy always prefer the wrong one (the last one)
instead there's the Cloudflare's Cf-Connecting-Ip which is more accurate than the X-Forwarded-For header
it would be great if this could be added

or an alternative fix, if there is multiple IP in the X-Forwarded-For header always prefer the public IP over private ones
because in my case here's how the header looks like: X-Forwarded-For: <mypublicip>, 172.17.0.1(docker)

💻 Target components
ntfy server

Originally created by @ChokunPlayZ on GitHub (Oct 1, 2023). Original GitHub issue: https://github.com/binwiederhier/ntfy/issues/905 :bulb: **Idea** I think there's not many configs like mine where the setup involves multiple reverse proxy (in my case NGINX and Cloudflare) the current "behind proxy" setting cause issue because NGINX attach 2 IP in the forwarded for header, ntfy always prefer the wrong one (the last one) instead there's the Cloudflare's `Cf-Connecting-Ip` which is more accurate than the `X-Forwarded-For` header it would be great if this could be added or an alternative fix, if there is multiple IP in the `X-Forwarded-For` header always prefer the public IP over private ones because in my case here's how the header looks like: `X-Forwarded-For: <mypublicip>, 172.17.0.1(docker)` :computer: **Target components** ntfy server
BreizhHardware 2026-05-07 00:26:10 +02:00
Author
Owner

@binwiederhier commented on GitHub (Nov 18, 2023):

I understand that the X-Forwarded-For handling is rudimentary. ntfy picks the rightmost address, since that is the most secure way to pick a trustworthy address. See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-For for details.

To implement a proper X-Forwarded-For handling, we'd need to be able to configure a list of trustworthy proxies, e.g. proxies: 172.17.0.1. These proxies would be ignored in the selection of the IP address.

Adding a vendor-specific header like Cf-Connecting-Ip doesn't seem like a great idea, IMHO.

<!-- gh-comment-id:1817304272 --> @binwiederhier commented on GitHub (Nov 18, 2023): I understand that the X-Forwarded-For handling is rudimentary. ntfy picks the rightmost address, since that is the most secure way to pick a trustworthy address. See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-For for details. To implement a proper X-Forwarded-For handling, we'd need to be able to configure a list of trustworthy proxies, e.g. `proxies: 172.17.0.1`. These proxies would be ignored in the selection of the IP address. Adding a vendor-specific header like `Cf-Connecting-Ip` doesn't seem like a great idea, IMHO.
Author
Owner

@hipur commented on GitHub (Nov 8, 2024):

Maybe X-Real-IP could be beneficial, since it's easier to overwrite without reverse proxies adding another IP to the right. e.g.: traefik

<!-- gh-comment-id:2465881869 --> @hipur commented on GitHub (Nov 8, 2024): Maybe X-Real-IP could be beneficial, since it's easier to overwrite without reverse proxies adding another IP to the right. e.g.: traefik
Author
Owner

@ojio-san commented on GitHub (Dec 29, 2024):

Hello there,

I'm sorry to revive such an old topic, but this issue is open, so I would like to add something to this, as I have multiple proxies there, so I have the same issue as @ChokunPlayZ

@binwiederhier

I understand that the X-Forwarded-For handling is rudimentary. ntfy picks the rightmost address, since that is the most secure way to pick a trustworthy address. See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-For for details.

I would like to know where did you find that the rightmost address is the most secure way and the trustworthy address ?

If I'm reading right here : https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-For#client

This means that the rightmost IP address is the IP address of the most recent proxy and the leftmost IP address is the address of the originating client

So, if we get the rightmost address, we always get the proxy, not the client, because "the leftmost IP address is the address of the originating client", right ?

In that case, the visitor_ip is always the last proxy IP and not the client IP; I don't think that's the expected behaviour.

ntfy-857c5c874d-x5mvp 2024/12/29 09:57:50 DEBUG HTTP request finished (http_method=GET, http_path=/, tag=http, time_taken_ms=0, visitor_auth_limiter_limit=0.016666666666666666, visitor_auth_limiter_tokens=3 │
│ 0, visitor_id=ip:2a03:123:456:789::123, visitor_ip=2a03:123:456:789::123, visitor_messages=0, visitor_messages_limit=17280, visitor_messages_remaining=17280, visitor_request_limiter_limit=0.2, visitor_reque │
│ st_limiter_tokens=60, visitor_seen=2024-12-29T08:57:50.115+01:00) 

Thank you, have a nice day :)

<!-- gh-comment-id:2564658251 --> @ojio-san commented on GitHub (Dec 29, 2024): Hello there, I'm sorry to revive such an old topic, but this issue is open, so I would like to add something to this, as I have multiple proxies there, so I have the same issue as @ChokunPlayZ @binwiederhier > I understand that the X-Forwarded-For handling is rudimentary. ntfy picks the rightmost address, since that is the most secure way to pick a trustworthy address. See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-For for details. I would like to know where did you find that the rightmost address is the most secure way and the trustworthy address ? If I'm reading right here : https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-For#client `This means that the rightmost IP address is the IP address of the most recent proxy and the leftmost IP address is the address of the originating client` So, if we get the rightmost address, we always get the proxy, not the client, because "the leftmost IP address is the address of the originating client", right ? In that case, the `visitor_ip` is always the last proxy IP and not the client IP; I don't think that's the expected behaviour. ``` ntfy-857c5c874d-x5mvp 2024/12/29 09:57:50 DEBUG HTTP request finished (http_method=GET, http_path=/, tag=http, time_taken_ms=0, visitor_auth_limiter_limit=0.016666666666666666, visitor_auth_limiter_tokens=3 │ │ 0, visitor_id=ip:2a03:123:456:789::123, visitor_ip=2a03:123:456:789::123, visitor_messages=0, visitor_messages_limit=17280, visitor_messages_remaining=17280, visitor_request_limiter_limit=0.2, visitor_reque │ │ st_limiter_tokens=60, visitor_seen=2024-12-29T08:57:50.115+01:00) ``` Thank you, have a nice day :)
Author
Owner

@pixitha commented on GitHub (Jan 2, 2025):

I just ran across this issue when I was deploying ntfy on Fly.io, which uses a customer header name like Cloudflare does. Right now with behind-proxy: true, I just get the Fly.io proxy IP, not the true client ip.

So I think that there are 2 issues here: picking the incorrect XFF client IP from the list and the lack of support for multiple/alternate header names.

It would be great if setting behind proxy to true, would also enable the option to set which header to look at for the client IP? (cloudflare/fly.io/aws/gcp all have customer header names to store client ip securely)
OR
if you could configured a trusted proxy subnet or IP so that looking at the XFF IP list, you could know to ignore the far right address if its "internal" (This is what Apache Tomcat does)

Example Image to help visualize the possible problem:
image
ref: https://www.stackhawk.com/blog/do-you-trust-your-x-forwarded-for-header/

In this diagram, picking the right most IP, would always be the first proxy, but really we should be picking n-1 if there are more than 1 IPs in the list?

<!-- gh-comment-id:2567246018 --> @pixitha commented on GitHub (Jan 2, 2025): I just ran across this issue when I was deploying ntfy on Fly.io, which uses a customer header name like Cloudflare does. Right now with behind-proxy: true, I just get the Fly.io proxy IP, not the true client ip. So I think that there are 2 issues here: picking the incorrect XFF client IP from the list and the lack of support for multiple/alternate header names. It would be great if setting behind proxy to true, would also enable the option to set which header to look at for the client IP? (cloudflare/fly.io/aws/gcp all have customer header names to store client ip securely) OR if you could configured a trusted proxy subnet or IP so that looking at the XFF IP list, you could know to ignore the far right address if its "internal" (This is what [Apache Tomcat](https://github.com/apache/tomcat/blob/c3511b1baf7defc57923fce7e12b5e6b392c68c5/java/org/apache/catalina/filters/RemoteIpFilter.java#L788-L800) does) Example Image to help visualize the possible problem: ![image](https://github.com/user-attachments/assets/5c0bd13e-4df4-4722-9c73-5da9a02064ed) ref: https://www.stackhawk.com/blog/do-you-trust-your-x-forwarded-for-header/ In this diagram, picking the right most IP, would always be the first proxy, but really we should be picking n-1 if there are more than 1 IPs in the list?
Author
Owner

@pixitha commented on GitHub (Jan 6, 2025):

I'm working on a PR for this, here is an example running in fly.io:

2025-01-06T02:17:30.515 app[28669d] den [info] 2025/01/06 02:17:30 DEBUG Starting IP extraction (http_method=GET, http_path=/config.js, tag=http)

2025-01-06T02:17:30.515 app[28669d] den [info] 2025/01/06 02:17:30 DEBUG RemoteAddr: 172.16.13.122:52970 (http_method=GET, http_path=/config.js, tag=http)

2025-01-06T02:17:30.515 app[28669d] den [info] 2025/01/06 02:17:30 DEBUG Initial IP after RemoteAddr parsing: 172.16.13.122 (http_method=GET, http_path=/config.js, tag=http)

2025-01-06T02:17:30.515 app[28669d] den [info] 2025/01/06 02:17:30 DEBUG Using ProxyClientIPHeader: Fly-Client-IP (http_method=GET, http_path=/config.js, tag=http)

2025-01-06T02:17:30.515 app[28669d] den [info] 2025/01/06 02:17:30 DEBUG Custom header Fly-Client-IP value: 76.123.x.x(http_method=GET, http_path=/config.js, tag=http)

2025-01-06T02:17:30.515 app[28669d] den [info] 2025/01/06 02:17:30 DEBUG Successfully parsed IP from custom header: 76.123.x.x(http_method=GET, http_path=/config.js, tag=http)

2025-01-06T02:17:30.515 app[28669d] den [info] 2025/01/06 02:17:30 DEBUG Final resolved IP: 76.123.x.x(http_method=GET, http_path=/config.js, tag=http)

2025-01-06T02:17:30.515 app[28669d] den [info] 2025/01/06 02:17:30 DEBUG HTTP request started (http_method=GET, http_path=/config.js, tag=http, visitor_auth_limiter_limit=0.016666666666666666, visitor_auth_limiter_tokens=30, visitor_id=ip:76.123.x.x, visitor_ip=76.123.x.x, visitor_messages=0, visitor_messages_limit=17280, visitor_messages_remaining=17280, visitor_request_limiter_limit=0.2, visitor_request_limiter_tokens=60, visitor_seen=2025-01-06T02:17:30.514Z)
<!-- gh-comment-id:2572082721 --> @pixitha commented on GitHub (Jan 6, 2025): I'm working on a PR for this, here is an example running in fly.io: ``` 2025-01-06T02:17:30.515 app[28669d] den [info] 2025/01/06 02:17:30 DEBUG Starting IP extraction (http_method=GET, http_path=/config.js, tag=http) 2025-01-06T02:17:30.515 app[28669d] den [info] 2025/01/06 02:17:30 DEBUG RemoteAddr: 172.16.13.122:52970 (http_method=GET, http_path=/config.js, tag=http) 2025-01-06T02:17:30.515 app[28669d] den [info] 2025/01/06 02:17:30 DEBUG Initial IP after RemoteAddr parsing: 172.16.13.122 (http_method=GET, http_path=/config.js, tag=http) 2025-01-06T02:17:30.515 app[28669d] den [info] 2025/01/06 02:17:30 DEBUG Using ProxyClientIPHeader: Fly-Client-IP (http_method=GET, http_path=/config.js, tag=http) 2025-01-06T02:17:30.515 app[28669d] den [info] 2025/01/06 02:17:30 DEBUG Custom header Fly-Client-IP value: 76.123.x.x(http_method=GET, http_path=/config.js, tag=http) 2025-01-06T02:17:30.515 app[28669d] den [info] 2025/01/06 02:17:30 DEBUG Successfully parsed IP from custom header: 76.123.x.x(http_method=GET, http_path=/config.js, tag=http) 2025-01-06T02:17:30.515 app[28669d] den [info] 2025/01/06 02:17:30 DEBUG Final resolved IP: 76.123.x.x(http_method=GET, http_path=/config.js, tag=http) 2025-01-06T02:17:30.515 app[28669d] den [info] 2025/01/06 02:17:30 DEBUG HTTP request started (http_method=GET, http_path=/config.js, tag=http, visitor_auth_limiter_limit=0.016666666666666666, visitor_auth_limiter_tokens=30, visitor_id=ip:76.123.x.x, visitor_ip=76.123.x.x, visitor_messages=0, visitor_messages_limit=17280, visitor_messages_remaining=17280, visitor_request_limiter_limit=0.2, visitor_request_limiter_tokens=60, visitor_seen=2025-01-06T02:17:30.514Z) ```
Author
Owner

@sr7142x commented on GitHub (Jan 21, 2025):

@pixitha I have the exact problem and your changes looks promising.

<!-- gh-comment-id:2605268654 --> @sr7142x commented on GitHub (Jan 21, 2025): @pixitha I have the exact problem and your changes looks promising.
Author
Owner

@ojio-san commented on GitHub (Jan 29, 2025):

I'm also looking forward @pixitha PR merged on master !
@binwiederhier : is it possible to check https://github.com/binwiederhier/ntfy/pull/1252 ?
Thank you :)

<!-- gh-comment-id:2622135007 --> @ojio-san commented on GitHub (Jan 29, 2025): I'm also looking forward @pixitha PR merged on master ! @binwiederhier : is it possible to check https://github.com/binwiederhier/ntfy/pull/1252 ? Thank you :)
Author
Owner

@ojio-san commented on GitHub (Nov 20, 2025):

It's a bit late, but it's working fine for me with this

behind-proxy: true
proxy-forwarded-header: "X-Your-Header-With-The-Real-Client-IP"
proxy-trusted-hosts: "1.2.3.4,5.6.7.8,2001:db8:3333:4444:5555:6666:7777:8888"

Thank you

<!-- gh-comment-id:3556722947 --> @ojio-san commented on GitHub (Nov 20, 2025): It's a bit late, but it's working fine for me with this ``` behind-proxy: true proxy-forwarded-header: "X-Your-Header-With-The-Real-Client-IP" proxy-trusted-hosts: "1.2.3.4,5.6.7.8,2001:db8:3333:4444:5555:6666:7777:8888" ``` Thank you
Author
Owner

@binwiederhier commented on GitHub (Nov 20, 2025):

Yes, this has been released a while ago. https://docs.ntfy.sh/config/#behind-a-proxy-tls-etc

<!-- gh-comment-id:3557243525 --> @binwiederhier commented on GitHub (Nov 20, 2025): Yes, this has been released a while ago. https://docs.ntfy.sh/config/#behind-a-proxy-tls-etc
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
starred/ntfy#639
No description provided.