Introduction
Cloudflare is great as a quick-and-easy CDN and DDoS protection, but one downside is that the IP address seen by your web server will be that of the Clouflare proxy and not the actual client.
This can be a big security issue because your Apache access and error logs will only show the IP of the Cloudflare proxy and not the actual client. Also, if you have IP restrictions in place defined in .htaccess
or a PHP script, these will not work.
Cloudflare sends the real client IP as CF-Connecting-IP
in the HTTP header, and we can pass this on to PHP or Apache using mod_remoteip
.
Note: Cloudflare’s own Apache mod mod_cloudflare
is now redundant and discontinued as Apache’s own mod mod_remoteip
performs the same function.
PHP Script
You can easily fetch the real client IP in PHP with no further configuration required.
echo $_SERVER["HTTP_CF_CONNECTING_IP"]; // real IP of visitor
echo $_SERVER["HTTP_CF_IPCOUNTRY"]; //country of visitor
If you do this, and the validity of the visiting IP address is important, you might need to verify that the $_SERVER["REMOTE_ADDR"]
contains an actual Cloudflare proxy IP address, because anyone can fake the header if they are able to connect directly to the server. See “Trusted Proxies” down the page for more info.
Apache
This configuration was tested on Ubuntu 20.04 and 18.04, but the process should be similar for any Debian-based web servers.
This method also works for Virtual Hosts. If you have multiple sites hosted and some do not use Cloudflare, Apache will simply default back to the Remote Address (REMOTE_ADDR
).
Enable remoteip mod
Firstly, enable the remoteip
mod.
sudo a2enmod remoteip
Restart Apache:
sudo systemctl restart apache2
Edit Config and Define Trusted Proxies
In order to pass the real client IP address from Cloudflare to Apache, we need to define the RemoteIPHeader
directive as CF-Connecting-IP
in the remoteip
configuration file /etc/apache2/conf-enabled/remoteip.conf
.
Create the remoteip.conf
configuration file:
sudo nano /etc/apache2/conf-enabled/remoteip.conf
Simply add RemoteIPHeader CF-Connecting-IP
as the first line and then a list of trusted Cloudflare proxies below it (RemoteIPTrustedProxy
). As it’s easy for hackers to spoof CF-Connecting-IP
in the http header, we need to make sure that Apache knows which proxies to trust. Cloudflare keeps an updated list of these proxies here: Cloudflare IPv4 Proxies and Cloudflare IPv6 Proxies.
RemoteIPHeader CF-Connecting-IP
RemoteIPTrustedProxy 173.245.48.0/20
RemoteIPTrustedProxy 103.21.244.0/22
RemoteIPTrustedProxy 103.22.200.0/22
RemoteIPTrustedProxy 103.31.4.0/22
RemoteIPTrustedProxy 141.101.64.0/18
RemoteIPTrustedProxy 108.162.192.0/18
RemoteIPTrustedProxy 190.93.240.0/20
RemoteIPTrustedProxy 188.114.96.0/20
RemoteIPTrustedProxy 197.234.240.0/22
RemoteIPTrustedProxy 198.41.128.0/17
RemoteIPTrustedProxy 162.158.0.0/15
RemoteIPTrustedProxy 104.16.0.0/12
RemoteIPTrustedProxy 172.64.0.0/13
RemoteIPTrustedProxy 131.0.72.0/22
RemoteIPTrustedProxy 2400:cb00::/32
RemoteIPTrustedProxy 2606:4700::/32
RemoteIPTrustedProxy 2803:f800::/32
RemoteIPTrustedProxy 2405:b500::/32
RemoteIPTrustedProxy 2405:8100::/32
RemoteIPTrustedProxy 2a06:98c0::/29
RemoteIPTrustedProxy 2c0f:f248::/32
Save and exit (press CTRL
+ X
, press Y
and then press ENTER
)
Restart Apache:
sudo systemctl restart apache2
If Apache doesn’t detect CF-Connecting-IP
in the HTTP header (e.g. if Cloudflare is turned off or not configured for a particular Virtual Host), it will fall back to the Remote Address (REMOTE_ADDR
).
If Apache does detect CF-Connecting-IP
but it is coming from an IP not defined in RemoteIPTrustedProxy
(e.g. a hacker trying to spoof the headers), Apache will fall back to the Remote Address (REMOTE_ADDR
).
Apache Access and Error Logs (optional)
You may not need to complete this step, but If you find that your access and error logs are showing the Cloudflare proxy IP instead of the remote user IP, you may need to do some further configuration.
Visit a page on your website in order to create an access log entry, then view the access log to see if it shows your IP or the Cloudfare proxy IP:
sudo tail /var/log/apache2/access.log -n 200
If it is not showing your IP, continue below.
In order for the real client IP (CF-Connecting-IP
) to appear in access.log
and error.log
, we must make a small change to apache2.conf
.
Edit apache2.conf
:
sudo nano /etc/apache2/apache2.conf
Press CTRL
+ W
and search for LogFormat
.
The default log format should look something similar to below:
LogFormat "%v:%p %h %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\"" vhost_combined
LogFormat "%h %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\"" combined
LogFormat "%h %l %u %t \"%r\" %>s %O" common
LogFormat "%{Referer}i -> %U" referer
LogFormat "%{User-agent}i" agent
The %h
variable in red here is the Remote IP. We just need to change this to %a
, which is the Client IP as defined by the mod_remoteip
module.
LogFormat "%v:%p %a %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\"" vhost_combined
LogFormat "%a %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\"" combined
LogFormat "%a %l %u %t \"%r\" %>s %O" common
LogFormat "%{Referer}i -> %U" referer
LogFormat "%{User-agent}i" agent
Save and exit (press CTRL
+ X
, press Y
and then press ENTER
)
Restart Apache:
sudo systemctl restart apache2
To view the access log to see if it is now reporting CF-Connecting-IP
:
sudo tail /var/log/apache2/access.log -n 200
Make sure that the Cloudlfare Proxy IP does not appear in the access log, but instead your own client IP.
If Apache doesn’t detect CF-Connecting-IP
in the HTTP header (e.g. if Cloudflare is turned off or not configured for a particular Virtual Host), the log will fall back to the Remote Address (REMOTE_ADDR
).
If Apache does detect CF-Connecting-IP
but it is coming from an IP not defined in RemoteIPTrustedProxy
(e.g. a hacker trying to spoof the headers), the log will fall back to the Remote Address (REMOTE_ADDR
).
Let me know if this helped. Follow me on Twitter, Facebook and YouTube, or 🍊 buy me a smoothie.
Mine seems to work fine with the default %h in the LogFormat line so I guess I’ll just leave it as is. 😅
Yes, something has changed since I wrote this article. It seems you no longer need to complete this step. Article updated.
Don’t forget to add your own IPv4 network (and IPv6 too, if you have it), otherwise you’ll just see your router’s LAN address appearing in the Apache logs.
If you use any of the following RFC1918 addresses for your LAN IP addressing, just add the whole block to exclude it, viz:
10.0.0.0/8
173.172.16.0.0/12
192.168.0.0/16
If you use IPv6, just add the /48 prefix that your ISP assigns to you, and that will also cover any subnets you carved out of a /64 as well (if you were that adventurous to begin with).
Thanks man! I’m using cloudflared tunnel and have to do this…
Awesome life-saver of an article — we suddenly had no way to identify bad actors on any of our websites – which kind of defeats the purpose of Cloudflare as far as a WAF goes… mod_remoteip replacing mod_cloudflare did the trick! In a sea of useless repeated affiliate nonsense – this post was an oasis – the clouds parted, the sun shone and all was well…
Awesome, glad it worked!
I could not this working with the Cloudflare guide, it said add this /etc/apache2/conf-available/remoteip.conf
you said /etc/apache2/conf-enabled/remoteip.conf and it works 🙂
Thank you sooooo much 🙂
Great! 😎
Hi,
how is it possible to get the cd country code into the response header. I tryed a lot but didnt find a working solution.
Awesome explanation! Working on vhosts as intended 🙂
Thank you very much! Helped me a lot!
Clear and simple instructions/explanations, like we love (at least I love it !).
It indeed was very helpful. Thanks.
Hi there,
I want to ask, if we use this mod today, can past log (yesterday log) change to show the origin IP Address?
Thank you. 🙂
Hi there. No, unfortunately it is not retroactive.