Ban Wordpress bruteforce attacks with Fail2ban

door sign with "stage door band and crew only" written on
it Nowadays, bruteforce attacks on Wordpress blogs are common, and defending against them is quite documented. However, I feel my setup is unusual, so I thought it was worth a blog post. What's the goal ? I want to stop Wordpress bruteforce attacks using Fail2ban.

So you just have to install this little plugin...

Yeah, thank you but no thank you. One of my constraints is that I don't have control over the Wordpress installations I want to protect. So I'd rather not use WP fail2ban. Another thing that annoys me is some of the late reviews indicating the plugin is not so great. Hopefully, there is a work around.

About Wordpress authentication

In my research, I stumbled upon an article giving details on the authentication pages in Wordpress, wp-login.php and xmlrpc.php, but I could not find it again. Instead, I tried it for myself and noticed, when a POST request is sent to these urls, the HTTP status code is:

  • 200 when authentication has failed;
  • 302 when authentication is successful.

We can now build a fail2ban filter around this, by looking for POST requests on wp-login.php or xmlrpc.php that get a status code of 200 in Apache's access log file.

Fail2ban filter configuration

The fail2ban configuration directory contains a sub-directory named filter.d, where all the filters are. That's were I added my custom filter, in a file called wordpress.conf (very original, isn't it ?) :

failregex = ^<HOST>\ \-.*\"POST\ \/(wp-login\.php|xmlrpc\.php) HTTP\/1\..*\"
ignoreregex =

The regular expression is tailored to my Apache access log file format, which is "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"", and defined in the LogFormat directive.

Fail2ban jail configuration

Now that the filter is ready, time to create the jail itself. As for the filters, there is a jail.d sub-directory, where I created a dedicated file for the jail :

enabled = true
filter = wordpress
logpath = /path/to/website/log/access.log
maxretry = 10

Something interesting, multiple jails can use the same filter. In my case, this can be useful, I can create one jail for each hosted Wordpress blog, and regroup them in the same jail file.

Fail2ban usage

Now, let's restart the fail2ban service to get the new configurations applied, and a few seconds later, check everything is working with the command : fail2ban-client status wordpress-jail. In this example, the jail is named wordpress-jail but it can be something else. The output should look like the following (of course this one has been redacted):

Status for the jail: wordpress-jail
|- Filter
|  |- Currently failed: 4
|  |- Total failed:     4
|  `- File list:        /path/to/website/log/access.log
`- Actions
   |- Currently banned: 3
   |- Total banned:     3
   `- Banned IP list:

I hope you enjoyed this post ! If you did, please share it on your favorite social networks :-)

Photo by seabass creatives on Unsplash.