The Problem Everyone Googles Wrong
You’re running WordPress behind Cloudflare with Fail2Ban for security. You fix NGINX to log real visitor IPs instead of Cloudflare’s proxy IPs. Everything works great. Then suddenly, legitimate users (including you) randomly get “not authorized” errors. Refresh the page, it works. Wait five minutes, happens again.
You check the logs. Fail2Ban is banning your users for “Untrusted X-Forwarded-For header” violations.
Most tutorials will tell you to disable Fail2Ban’s WordPress filter or add whitelists. That’s treating symptoms. Here’s what’s actually broken—and how to fix it properly.
The Real Problem: Three Security Systems Having a Conversation
- Cloudflare receives visitor traffic and proxies it to your server, adding headers:
CF-Connecting-IP: The real visitor IP (single, trusted value)X-Forwarded-For: The proxy chain (can contain multiple IPs)
- NGINX is configured to trust Cloudflare’s IP ranges and extract the real IP from
CF-Connecting-IP - WordPress/PHP sees the
X-Forwarded-Forheader and logs warnings about it being “untrusted” because it doesn’t match expected patterns - Fail2Ban reads those warnings, sees repeated “Untrusted” messages, thinks it’s an attack pattern, bans the IP
Result: Your security system bans legitimate users because it’s confused about which proxy header to trust.
The Fix: Tell Fail2Ban What’s Actually an Attack
Don’t edit /etc/fail2ban/filter.d/wordpress-hard.conf directly—updates will overwrite it.
Create a local override:
sudo nano /etc/fail2ban/filter.d/wordpress-hard.local
Add this single line:
[Definition]
ignoreregex = Untrusted X-Forwarded-For
Restart Fail2Ban:
sudo systemctl restart fail2ban
Done. The “not authorized” errors stop.
But Wait—Aren’t We Creating a Security Hole?
Short answer: No, if you did this right.
Long answer: The “Untrusted X-Forwarded-For” warnings are noise, not attacks. Here’s why:
- You’re using
real_ip_header CF-Connecting-IPin NGINX (notX-Forwarded-For) - Cloudflare sets
CF-Connecting-IPreliably for every request - The
X-Forwarded-Forheader exists alongside it but isn’t used for IP detection - WordPress/PHP logs warnings about seeing it, but it doesn’t matter
The actual security hole would be direct access to your server bypassing Cloudflare, where attackers could inject fake headers. But you’ve already handled that (right?) by restricting your firewall to only accept traffic from Cloudflare’s IP ranges.
If you haven’t done that yet:
# Verify only Cloudflare IPs can reach ports 80/443
sudo ufw status
# or
sudo iptables -L -n
No Cloudflare IP restrictions? Fix that first. The Fail2Ban workaround won’t matter if attackers can bypass Cloudflare entirely.
The Complete NGINX Configuration (For Reference)
Your NGINX config should explicitly trust Cloudflare’s IP ranges:
# Cloudflare IP ranges - update periodically from cloudflare.com/ips/
set_real_ip_from 173.245.48.0/20;
set_real_ip_from 103.21.244.0/22;
set_real_ip_from 103.22.200.0/22;
set_real_ip_from 103.31.4.0/22;
set_real_ip_from 141.101.64.0/18;
set_real_ip_from 108.162.192.0/18;
set_real_ip_from 190.93.240.0/20;
set_real_ip_from 188.114.96.0/20;
set_real_ip_from 197.234.240.0/22;
set_real_ip_from 198.41.128.0/17;
set_real_ip_from 162.158.0.0/15;
set_real_ip_from 104.16.0.0/13;
set_real_ip_from 104.24.0.0/14;
set_real_ip_from 172.64.0.0/13;
set_real_ip_from 131.0.72.0/22;
real_ip_header CF-Connecting-IP;
real_ip_recursive on;
Add IPv6 ranges if you’re using them. Test with sudo nginx -t before reloading.
What We Actually Fixed
Most people treat this as “Fail2Ban is too aggressive” and start whitelisting IPs or disabling filters. That’s lazy.
The real issue: three security systems were looking at different signals and reaching different conclusions about what’s legitimate traffic. We didn’t disable security—we clarified which signal to trust.
- NGINX trusts:
CF-Connecting-IPfrom Cloudflare IP ranges - Fail2Ban ignores: PHP warnings about other proxy headers
- Firewall enforces: Only Cloudflare can reach the server
Security through clarity, not through hope.
LittleBig.Co — We don’t build what you ask for. We fix what’s actually broken.