If you’re running Discourse behind Cloudflare, the default IP that Discourse sees ends up being Cloudflare’s IP (or the Docker bridge IP) unless you explicitly pass the real user IP.
What’s happening now
Your stack is:
Cloudflare CDN
↓
Nginx (host)
↓
Discourse Docker Nginx
Discourse tries to determine the client IP in this order:
- X-Forwarded-For
- X-Real-IP
- REMOTE_ADDR
But if your host Nginx doesn’t set X-Forwarded-For, it defaults to $remote_addr, which will be Cloudflare’s IP, not the user’s IP. That gets passed down to the Discourse container, so Discourse never sees the actual visitor IP.
Cloudflare already adds a header called CF-Connecting-IP that contains the real user IP. If your host Nginx picks that up and forwards it properly, Discourse will finally see the correct client IP.
What you need
In your host Nginx config, set:
proxy_set_header X-Forwarded-For $http_cf_connecting_ip;
proxy_set_header X-Real-IP $http_cf_connecting_ip; # Optinal
Now the real user IP flows into the container.
ASCII Flow Chart
User
|
v
Cloudflare CDN
|
v
Host Nginx
| (receives CF-Connecting-IP from Cloudflare)
| sets:
| X-Forwarded-For = $http_cf_connecting_ip
| X-Real-IP = $http_cf_connecting_ip
|
v
Discourse Docker Nginx
| (reads headers in this priority)
| 1) X-Forwarded-For
| 2) X-Real-IP
| 3) REMOTE_ADDR (fallback)
|
v
Discourse app