Properly configure WordPress, Fail2Ban and CloudFlare to stop hacker bots


WordPress is probably the most used content management system. CloudFlare helps reduce load on your server with a free content delivery network for static assets. Fail2Ban keeps the persistent bots at bay. All together, they are a great combination for running, optimizing and securing a site.

Recently my WordPress login pages were hammered by bots. I run all my sites on a VPS so I decided to dig into Fail2Ban and get them secured. I discovered some major challenges with the default configuration and my use of CloudFlare and found solutions to get everything running optimally.

Problems and solutions

Problem #1

WordPress login pages (and xmlrpc.php – a remote-access API with authentication) are not protected by Fail2Ban by default. This lets bots hammer them endlessly, sometimes to the point of causing huge bandwidth and resource use and denial of service.

Solution #1

If you're not managing your blog from remote apps or services, block xmlrpc.php via the .htaccess file in your site's main web folder, usually public_html. This reduces the amount of bot connections hitting PHP and MySQL on your server, saving resources. Your .htaccess file would look something like this (note: full WordPress section is omitted, don't change it.):

RewriteEngine On
# block xmlrpc
RewriteCond %{REQUEST_FILENAME} xmlrpc\.php 
RewriteRule .* - [L,R=404]

# BEGIN WordPress
...

Install Fail2Ban. On Debian/Ubuntu servers this is done with sudo apt-get install fail2ban. (Note: If you're still on Ubuntu 16.04 I recommend upgrading to a later version than available in the repositories, as described here, for performance improvements and IPv6 support.)

Copy /etc/fail2ban/jail.conf to /etc/fail2ban/jail.local and make your changes in jail.local. Anything not overridden by jail.local will be loaded from jail.conf, so you can safely remove anything you're not using.

Bonus: Install ipset sudo apt-get install ipset for much more efficient banning, where ban lists are stored in memory and blocked with one iptables rule instead of hundreds and thousands of iptables rules. Set banaction = iptables-ipset-proto6 and banaction_allports = iptables-ipset-proto6-allports in the [DEFAULT] section at the top of jail.local to enable it.

Set up a custom WordPress filter in Fail2Ban. Create /etc/fail2ban/filters.d/wordpress.conf as follows:

# Fail2Ban configuration file
[Definition]
failregex = ^<HOST> -.*"(GET|POST|HEAD) /wp-login.php.*$
^<HOST> -.*"(GET|POST|HEAD) /xmlrpc.php.*$
ignoreregex =

Create and enable the jail in jail.local, with the correct path to your web site log files:

[wordpress]
port = http,https
logpath = /var/www/*/logs/access*log
enabled = true

Set up and enable a recidive jail in jail.local for repeat offenders:

[recidive]
logpath = /var/log/fail2ban.log
banaction = %(banaction_allports)s
bantime = 2w
findtime = 1d

Note dbpurgeage in /etc/fail2ban/fail2ban.conf should be a day or two longer than the recidive jail bantime. Older version of Fail2Ban may require you to use seconds for all values (just numbers, no s at the end).

Restart Fail2Ban with service fail2ban restart after making changes.

Problem #2

If you set up Fail2Ban to protect WordPress with a custom jail, and your protected URLs are accessible through a CloudFlare proxy, Fail2Ban will ban CloudFlare IP addresses and break content delivery for your site.

Solution #2

Avoid proxying entire WordPress sites with CloudFlare. This is the only way to stop the bots. If they are able to route through CloudFlare you simply can't block them with Fail2Ban, even if you set up mod_remoteip for apache. While mod_remoteip may show the proper visitor IP in the logs the connection is still through CloudFlare and therefore cannot be blocked by Fail2Ban firewall entries. Set up a subdomain such as cdn.example.com that is alone proxied through CloudFlare, then use a plugin like CDN Enabler to route static WordPress assets through it.

Restrict any access to xmlrpc.php and wp-login.php through the CloudFlare proxy. This can be done with .htaccess in your main site folder. The following example blocks access to any PHP files or base folders through CloudFlare, which is only needed for static assets like images, scripts and CSS. Put it at the top of your .htaccess file or after the xmlrpc.php section mentioned above. If there's already a RewriteEngine On line defined above this that line can be skipped.

# stop bots hitting php files via cdn.* since we cant fail2ban them
# cdn php
RewriteEngine On
RewriteCond %{HTTP_HOST} cdn\..*\..*
RewriteCond %{REQUEST_FILENAME} (\.php$|^$)
RewriteRule .* - [L,R=404]
# cdn root
RewriteCond %{HTTP_HOST} cdn\..*\..*
RewriteCond %{REQUEST_URI} ^/$
RewriteRule .* - [L,R=404]

Make sure it's all working

Verify you can't access PHP pages through the CDN subdomain by browsing to e.g. https://cdn.example.com/wp-login.php.

Verify bots are being caught and blocked by following the Fail2Ban log file tail -f /var/log/fail2ban.log.

If you lock yourself out by accessing WordPress login pages too often use fail2ban-client unban <ip> from a shell.

Conclusion

That should help you secure your VPS or dedicated server running WordPress and CloudFlare!