4 min read

Getting real IP address from visitors when using Cloudflare as a reverse proxy

To be honest, I wasn't paying a lot of attention to my analytics, but I did notice one thing - there was a lot of users coming to this blog that all had similar IP addresses. I didn't really care too much about this, but it got me thinking. And then I had a Eureka! moment. Stupid, I know - but most of my websites are behind Cloudflare and I completely forgot that that also means that I will see Cloudflare's IP address in my visitor's log! That of course explains why all of those IP addresses looked similar, day after day after day... 😁

Luckily, Cloudflare does send us enough data when proxying for us to get the real IP address. CF-Connecting-IP parameter to the rescue!

This parameter is added by Cloudflare to preserve the original client's IP address, which would otherwise be masked by Cloudflare's IP due to the proxying process. When a user accesses a website through Cloudflare, the server typically only sees the IP address of Cloudflare's servers, not the end user's. To address this, Cloudflare includes the CF-Connecting-IP header in the request sent to the origin server. This header contains the original client's IP address, allowing for accurate logging, geolocation, rate limiting, and security checks based on the real user's IP rather than Cloudflare's.

Now, depending on your analytics tool, you will need to modify your config file to address this new parameter.

Originally, most analytics tools (or log analyzers) will use parameter HTTP_X_FORWARDED_FOR to identify users visiting your website. For example, there is a part of the config in Matomo, that does exactly this:

proxy_client_headers[] = "HTTP_X_FORWARDED_FOR"

To resolve the issue of capturing the user's real IP address, we need to instruct our analytics tool to use a different parameter, and for Matomo, that is actually quite easy:

proxy_client_headers[] = "HTTP_CF_CONNECTING_IP"

I restarted my Matomo instance after I made a change, and at that point, I started seeing real IP addresses in my visits log!

You can read more about restoring original visitor IPs here:

Restoring original visitor IPs · Cloudflare Support docs
When your website traffic is routed through the Cloudflare network , we act as a reverse proxy. This allows Cloudflare to speed up page load time by …

Restoring original visitor IPs

If you are still not getting real IP addresses after you make the change, take a look at your web server's log. In my case, I'm using traefik as my local reverse proxy, so I checked there:

{"ClientAddr":"","ClientHost":"","ClientPort":"56608","ClientUsername":"-","DownstreamContentSize":650,"DownstreamStatus":404,"Duration":11954620,"OriginContentSize":650,"OriginDuration":11927581,"OriginStatus":404,"Overhead":27039,"RequestAddr":"www.networktechguy.com","RequestContentSize":0,"RequestCount":63020,"RequestHost":"www.networktechguy.com","RequestMethod":"GET","RequestPath":"/contact/","RequestPort":"-","RequestProtocol":"HTTP/2.0","RequestScheme":"https","RetryAttempts":0,"RouterName":"{CNT1_NAME}@docker","ServiceAddr":"","ServiceName":"{CNT1_NAME}@docker","ServiceURL":"","StartLocal":"2024-07-05T16:46:20.363235592-04:00","StartUTC":"2024-07-05T20:46:20.363235592Z","TLSCipher":"TLS_CHACHA20_POLY1305_SHA256","TLSVersion":"1.3","downstream_Cache-Control":"no-cache, max-age=0, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0","downstream_Content-Encoding":"gzip","downstream_Content-Type":"text/html; charset=utf-8","downstream_Date":"Fri, 05 Jul 2024 20:46:20 GMT","downstream_Etag":"W/\"5e2-fhyxx09nYdNmm3YE+Oyy6dzD/0k\"","downstream_Vary":"Accept-Encoding","downstream_X-Powered-By":"Express","entryPointName":"websecure","level":"info","msg":"","origin_Cache-Control":"no-cache, max-age=0, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0","origin_Content-Encoding":"gzip","origin_Content-Type":"text/html; charset=utf-8","origin_Date":"Fri, 05 Jul 2024 20:46:20 GMT","origin_Etag":"W/\"5e2-fhyxx09nYdNmm3YE+Oyy6dzD/0k\"","origin_Vary":"Accept-Encoding","origin_X-Powered-By":"Express","request_Accept":"text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.2","request_Accept-Charset":"utf-8,windows-1251;q=0.7,*;q=0.6","request_Accept-Encoding":"gzip, br","request_Accept-Language":"en-us,en;q=0.7","request_Cdn-Loop":"cloudflare","request_Cf-Connecting-Ip":"","request_Cf-Ipcountry":"US","request_Cf-Ray":"89ea3430192414ee-LAX","request_Cf-Visitor":"{\"scheme\":\"https\"}","request_User-Agent":"Mozilla/5.0 (Windows NT 10.0; rv:45.0) Gecko/20100101 Firefox/45.0","request_X-Forwarded-Host":"www.networktechguy.com","request_X-Forwarded-Port":"443","request_X-Forwarded-Proto":"https","request_X-Forwarded-Server":"bd1a678aa226","request_X-Real-Ip":"","time":"2024-07-05T16:46:20-04:00"}

As you can see, default traefik logs don't look very nice, but we are not analyzing logs in this post, we are just looking for one particular parameter in all of this: request_Cf-Connecting-Ip, and here it is:


If you are getting something similar in your server's logs, then it means that Cloudflare is, indeed, sending the correct parameter. If it's still not working in your analytics tool, you may then want to research a little bit more there.

If you are not receiving this parameter at all and you're not seeing it in your logs, then the issue is most likely with your config on Cloudflare side. Focus your research on that instead.

And that is all - hopefully this will help you to get your user's real IP addresses (or as real as they can be).