bokumin.org

Github

Apache Real IP Logging with Cloudflare

This article is a translation of the following my article:

 

 

* Translated automatically by Google.
* Please note that some links or referenced content in this article may be in Japanese.
* Comments in the code are basically in Japanese.

 

by bokumin

 

Apache Real IP Logging with Cloudflare

 

Introduction

 

When you enable proxy on your Cloudflare record, Cloudflare acts as a reverse proxy. As a result, web server logs such as Apache only record Cloudflare’s IP address, making fail2ban and other access controls difficult.

 

Although it is possible to set a block on Cloudflare, in this article we will introduce a setting method to record the actual client IP address on the Apache side.

 

Verification environment

 

The versions of OpenSUSE and Apache are as follows.

 

uname -a
Linux 6.17.0-2-default #1 SMP PREEMPT_DYNAMIC Thu Oct  2 08:12:40 UTC 2025 (190326b) x86_64 x86_64 x86_64 GNU/Linux

sudo apachectl -v
[sudo] password for root: 
Server version: Apache/2.4.65 (Linux/SUSE)
Server built:   2025-09-23 13:42:03.000000000 +0000

 

Setup steps

 

First, enable the mod_remoteip module.

 

sudo a2enmod remoteip

sudo systemctl restart apache2

sudo apachectl -M | grep remoteip
 remoteip_module (shared)

 

Create a configuration file for cloudflare in/etc/apache2/conf.d/.

 

  • CF-Connecting-IP → Header sent by Cloudflare to capture the actual client IP address
  • RemoteIPTrustedProxy → IP address range to register as a trusted proxy

 

# cat /etc/apache2/conf.d/cloudflare.conf 
<IfModule remoteip_module>
    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/13
    RemoteIPTrustedProxy 104.24.0.0/14
    RemoteIPTrustedProxy 172.64.0.0/13
    RemoteIPTrustedProxy 131.0.72.0/22
</IfModule>

 

Let’s modify mod_log_config.conf. I think the default is %h, so change it to %a.

 

  • %h → remote hostname (or reverse hostname)
  • %a → Client IP address (actual IP when using mod_remoteip)

 

cat /etc/apache2/mod_log_config.conf 
#
# The following directives define some format nicknames for use with
# a CustomLog directive.
#
# https://httpd.apache.org/docs/2.4/mod/mod_log_config.html
#

#
#         Format string:                                Nickname:
#
LogFormat "%a %l %u %t \"%r\" %>s %b"                   common
LogFormat "%v %a %l %u %t \"%r\" %>s %b"                vhost_common
LogFormat "%{Referer}i -> %U"                           referer
LogFormat "%{User-agent}i"                              agent
LogFormat "%a %l %u %t \"%r\" %>s %b \
\"%{Referer}i\" \"%{User-Agent}i\""                     combined
LogFormat "%v %a %l %u %t \"%r\" %>s %b \
\"%{Referer}i\" \"%{User-Agent}i\""                     vhost_combined

# To use %I and %O, you need to enable mod_logio
<IfModule mod_logio.c>
LogFormat "%h %a %u %t \"%r\" %>s %b \
\"%{Referer}i\" \"%{User-Agent}i\" %I %O"               combinedio
</IfModule>

# Use one of these when you want a compact non-error SSL logfile on a virtual
# host basis:`
<IfModule mod_ssl.c>
Logformat "%t %a %{SSL_PROTOCOL}x %{SSL_CIPHER}x \
\"%r\" %b"                                              ssl_common
Logformat "%t %a %{SSL_PROTOCOL}x %{SSL_CIPHER}x \
\"%r\" %b \"%{Referer}i\" \"%{User-Agent}i\""           ssl_combined
</IfModule>

 

After changing the settings, reload Apache.
Check the log and if the actual client IP address is recorded, it is successful.

 

tail /var/log/apache2/access.log

xx.xx.x.xx - - [10/Nov/2025:11:21:57 +0900] "GET /robots.txt HTTP/1.1" 301 238 "-" "Mozilla/5.0 (compatible; MJ12bot/v1.4.8; http://mj12bot.com/)"
xx.xx.x.xx - - [10/Nov/2025:11:22:04 +0900] "GET /art-works/index.php?/search/psk-20250806-5eNDVvVRuC/created-weekly-list HTTP/1.1" 301 299 "-" "Mozilla/5.0 (compatible; MJ12bot/v1.4.8; http://mj12bot.com/)"
xx.xxx.xxx.xxx - - [10/Nov/2025:11:25:50 +0900] "GET /blog/2024/10/17/%E3%83%A1%E3%83%87%E3%82%A3%E3%82%A2%E3%82%B5%E3%83%BC%E3%83%90%E3%83%BC%EF%BC%88jellyfin%EF%BC%89%E3%82%92linux%E4%B8%8A%E3%81%A7%E6%A7%8B%E7%AF%89%E3%81%99%E3%82%8B%E6%96%B9%E6%B3%95/ HTTP/1.1" 301 429 "-" "Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko; compatible; bingbot/2.0; +http://www.bing.com/bingbot.htm) Chrome/116.0.1938.76 Safari/537.36"
xx.xxx.xxx.xxx - - [10/Nov/2025:11:27:01 +0900] "GET /art-works/picture.php?%2F6%2Ftags%2F3-sleepy%2F6-wallpaper%2F8-illust%2Fposted-monthly-list-2024-10-17 HTTP/1.1" 301 330 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Brave Chrome/85.0.4183.83 Safari/537.36"

 

Since Cloudflare’s IP ranges change regularly, it is useful to create an automatic update script.

 

 cat /root/bin/add_cloudflare.sh 
#!/bin/bash
# /usr/local/bin/update-cloudflare-ips.sh

CF_CONF="/etc/apache2/conf.d/cloudflare.conf"

IPV4=$(curl -s https://www.cloudflare.com/ips-v4)

cat > $CF_CONF << 'EOF'
<IfModule remoteip_module>
    RemoteIPHeader CF-Connecting-IP
    
EOF

for ip in $IPV4; do
    echo "    RemoteIPTrustedProxy $ip" >> $CF_CONF
done

echo "</IfModule>" >> $CF_CONF

apachectl configtest && systemctl reload apache2
Tomoko:/etc/apache2 # 

 

If you set this script to run with cron about once a month, you can always maintain the latest Cloudflare IP range.

 

 

The above is a method to record the real IP in Apache logs even via Cloudflare.

 

End

 

Referenced article:
https://developers.cloudflare.com/fundamentals/reference/http-headers/
https://zenn.dev/muchoco/articles/7ea510e0c285a7