If your WordPress website is proxied behind Cloudflare, the IP address reported to your server isn’t the actual IP address of the original request. It’s a Cloudflare IP. Most of the time, this doesn’t matter, but if you’re using WP fail2ban, this causes issues.

The Problem

By default, fail2ban in combination with WP fail2ban works by using iptables to block IP addresses that are trying (and failing) to log into your WordPress website.

When Cloudflare proxies your website, the incoming request is going to show a Cloudflare IP address. That’s the whole point of a proxy. That means that fail2ban is going to block that Cloudflare IP address.

But every request to your site is coming from Cloudflare IP addresses. Which means this will block a bunch of good traffic in addition to the bad. Oops.

What we need to do is tell WP fail2ban to log the original IP address, and then ensure that our web server is blocking requests based on the original IP address.

Configure WP fail2ban

First, we need to tell WP fail2ban what to log. Fortunately, the developer has already considered this. You can configure known IP addresses for WP fail2ban to handle by defining a WP_FAIL2BAN_PROXIES constant in your wp-config.php file. When this constant is configured, WP fail2ban will log the X-Forwarded-For request header instead of the reported remote IP. Here’s an example using Cloudflare’s IP addresses as of this writing:

define('WP_FAIL2BAN_PROXIES','173.245.48.0/20,103.21.244.0/22,103.22.200.0/22,103.31.4.0/22,141.101.64.0/18,108.162.192.0/18,190.93.240.0/20,188.114.96.0/20,197.234.240.0/22,198.41.128.0/17,162.158.0.0/15,104.16.0.0/13,104.24.0.0/14,172.64.0.0/13,131.0.72.0/22,2400:cb00::/32,2606:4700::/32,2803:f800::/32,2405:b500::/32,2405:8100::/32,2a06:98c0::/29,2c0f:f248::/32');
Code language: JavaScript (javascript)

Though infrequent, those IP addresses can change over time. For an automated solution that leverages Cloudflare’s API reporting of its own IP addresses, see this plugin we developed. Otherwise, Cloudflare reports any IP changes via various email and social media channels, so make sure you’re in the loop so you can respond appropriately.

Restore Original IP Addresses

If you only do this first step, you run into a situation where the correct IP addresses are banned, but the server still isn’t reading the correct IP address from the incoming requests, so it won’t actually block any traffic. So that’s pretty worthless.

To fix this, you also need to make sure your server is reading the correct IP address off of incoming requests.

I won’t rehash Cloudflare’s documentation on all of this, which includes documentation for Apache and other web servers. But I will quickly show how to get this working with Nginx.

Check for the Correct Module

First, your version of Nginx has to have been compiled with ngx_http_realip_module. While it isn’t technically a default module, services like Laravel Forge do include it. Run the following to get a formatted version of your Nginx configuration, including installed modules, and ensure it’s included (this tip found here):

nginx -V 2>&1 | tr ' ' '\n'
Code language: JavaScript (javascript)

Result:

nginx version: nginx/1.17.3 built with OpenSSL 1.1.1 ... --with-http_ssl_module --with-http_stub_status_module --with-http_realip_module --with-http_auth_request_module --with-http_v2_module --with-http_dav_module ...
Code language: JavaScript (javascript)

Create a Configuration

Next, we need to put Cloudflare’s IPs into a configuration for this module. I elected to create a dedicated conf file (on Forge, in a file at /etc/nginx/snippets/cloudflare_ips.conf). Cloudflare gives us the correct configuration with their IP addresses:

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 104.16.0.0/13; set_real_ip_from 104.24.0.0/14; set_real_ip_from 108.162.192.0/18; set_real_ip_from 131.0.72.0/22; set_real_ip_from 141.101.64.0/18; set_real_ip_from 162.158.0.0/15; set_real_ip_from 172.64.0.0/13; set_real_ip_from 173.245.48.0/20; set_real_ip_from 188.114.96.0/20; set_real_ip_from 190.93.240.0/20; set_real_ip_from 197.234.240.0/22; set_real_ip_from 198.41.128.0/17; set_real_ip_from 2400:cb00::/32; set_real_ip_from 2606:4700::/32; set_real_ip_from 2803:f800::/32; set_real_ip_from 2405:b500::/32; set_real_ip_from 2405:8100::/32; set_real_ip_from 2c0f:f248::/32; set_real_ip_from 2a06:98c0::/29; #use any of the following two real_ip_header CF-Connecting-IP; #real_ip_header X-Forwarded-For;
Code language: PHP (php)

Once again, this list will need to be updated when Cloudflare updates their IP addresses from time to time. Unfortunately, I don’t have a nifty, automated solution for that.

Reference Your New Config

Assuming, like me, you didn’t slap this directly into your main Nginx configuration, you want to reference this new configuration from your main Nginx conf file, within the server block. Once again, if you use Laravel Forge, this configuration will look familiar:

# FORGE CONFIG (DO NOT REMOVE!) include forge-conf/example.com/server/*; # Restore remote IPs include snippets/cloudflare_ips.conf;
Code language: PHP (php)

And you’re good to go!

Leave a Comment