A site behind Cloudflare is a common trap for fail2ban. Visitors reach the server not from their own addresses but from Cloudflare edge addresses, and if the web server logs those, the jail bans Cloudflare. Every visitor who came through that edge gets banned with it. Here's how to spot it, how to log the real IP, and why even then a firewall ban doesn't work the way you'd expect.
What the problem looks like
Out of the box nginx logs the address the TCP connection came from. For a site behind the Cloudflare proxy that's always a Cloudflare edge:
172.70.42.17 - - [09/Oct/2026:03:12:44 +0300] "POST /wp-login.php HTTP/1.1" 200 ...
172.70.42.17 - - [09/Oct/2026:03:12:45 +0300] "GET / HTTP/1.1" 200 ...
162.158.90.204 - - [09/Oct/2026:03:12:45 +0300] "GET /catalog/ HTTP/1.1" 200 ...
The first line is WordPress password guessing; the second and third are regular visitors. To fail2ban they're all the same “attacker” 172.70.42.17. A jail for wp-login.php with a threshold of 5 bans it within a minute, and some of the site's visitors start getting error 522.
This isn't hypothetical. On one of the servers running KIPBan log analysis, 37 of the 58 “attackers” matched by the WordPress pattern turned out to be Cloudflare addresses. Had a jail been enabled on that log without the real IP set up, it would have been banning Cloudflare.
Step 1. Log the real IP
Cloudflare passes the visitor's address in the CF-Connecting-IP header. The web server needs to be told it can trust that header, but only when the request comes from a Cloudflare address. Otherwise anyone could put someone else's address in it.
nginx
The ngx_http_realip_module is included in the nginx builds from the Debian, Ubuntu and RHEL repositories. Cloudflare publishes its ranges at cloudflare.com/ips, and they change occasionally, so it's convenient to generate the configuration with a script:
#!/bin/sh
# /usr/local/sbin/cloudflare-realip: refresh the list of Cloudflare edges for nginx
set -e
CONF=/etc/nginx/conf.d/cloudflare-realip.conf
TMP=$(mktemp)
{
for ip in $(curl -fsS https://www.cloudflare.com/ips-v4) $(curl -fsS https://www.cloudflare.com/ips-v6); do
echo "set_real_ip_from $ip;"
done
echo "real_ip_header CF-Connecting-IP;"
} > "$TMP"
grep -q set_real_ip_from "$TMP"
[ -f "$CONF" ] && cp "$CONF" "$CONF.bak"
mv "$TMP" "$CONF" && chmod 644 "$CONF"
if nginx -t 2>/dev/null; then
systemctl reload nginx
else
[ -f "$CONF.bak" ] && mv "$CONF.bak" "$CONF"
exit 1
fi
Run it weekly from cron. After nginx reloads, the log shows the real visitor addresses, and $remote_addr becomes the visitor's address for every directive, including allow and deny.
Apache
In Apache the same job is done by mod_remoteip:
a2enmod remoteip
# /etc/apache2/conf-available/cloudflare-remoteip.conf
RemoteIPHeader CF-Connecting-IP
RemoteIPTrustedProxy 173.245.48.0/20 103.21.244.0/22 103.22.200.0/22
# ... the remaining ranges from cloudflare.com/ips
And in LogFormat replace %h with %a: it's %a that prints the address mod_remoteip substituted.
Step 2. Understand what a firewall ban actually blocks
This is the part that usually goes unmentioned. Once the real IP is set up, fail2ban bans the right address, but it blocks it with iptables or nftables, that is, by the address of the TCP connection. And connections to a proxied site still come from Cloudflare edges. A banned visitor or bot keeps using the site through Cloudflare as if nothing happened.
In this setup a firewall ban only helps against requests sent straight to the server, bypassing Cloudflare. To stop requests that come through Cloudflare you need blocking at the HTTP level:
- A rule in Cloudflare itself. fail2ban ships the
cloudflareandcloudflare-tokenactions, which add the banned address to Cloudflare access rules through the API. - A refusal in nginx by the real IP. Once realip is set up, the
denydirective works on the visitor's address. The address list can be pulled in as a file withinclude.
And close direct access: if the server accepts connections on ports 80 and 443 only from Cloudflare addresses, nobody can bypass the proxy, and you no longer need firewall bans for the web at all.
If you can't set up the real IP
Sometimes the site is served by someone else's panel or proxy and you can't change the log. Then at least stop fail2ban from banning Cloudflare edges: put their ranges in ignoreip. It won't protect you from attacks coming through Cloudflare, but it won't break the site either.
How KIPBan handles it
- Log analysis warns you. If a site is behind Cloudflare and the log has Cloudflare addresses instead of visitors, the report says so, and for each pattern you can see how many of the addresses found belong to Cloudflare.
- The “Never ban Cloudflare IPs” option in the farm, group or server settings. Such a server doesn't ban Cloudflare addresses itself and doesn't receive them from the shared database. Turn it on for servers with sites behind Cloudflare, alongside the real IP setup, not instead of it. Servers without such sites don't need it: real scanners also come from Cloudflare addresses, for example from Cloudflare Workers.
- A block list without Cloudflare. Cloudflare addresses are excluded from the perimeter block list. Its
nginx.confformat is a ready list ofdenyrules which, with realip set up, blocks attackers even when they come through Cloudflare.
For what log analysis finds on ordinary servers, see What fail2ban on your server doesn't see.
Questions
Why do visitors get error 522 after a fail2ban ban?
Most likely the web server log contains Cloudflare edge addresses rather than visitors, and fail2ban banned an edge. All visitors coming through that edge can no longer reach the server. Set up the real IP via CF-Connecting-IP and unban the Cloudflare address.
Does iptables block a visitor behind Cloudflare if the log has their real IP?
No. The connection to the server still comes from a Cloudflare address, so an iptables rule for the visitor's address never matches. You need blocking at the HTTP level: a rule in Cloudflare or deny in nginx after setting up realip.
Where do I get the current list of Cloudflare addresses?
At cloudflare.com/ips, and as plain text at cloudflare.com/ips-v4 and cloudflare.com/ips-v6. The list changes occasionally, so it's best to refresh the web server configuration with a scheduled script.