How to get the real client IP in Discourse behind Cloudflare

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:

  1. X-Forwarded-For
  2. X-Real-IP
  3. 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