Fail2ban works by filtering a log file with a regular expression triggering a ban
action if the condition is met. After a preset time, it will trigger an unban
action. Without much effort, we can have WordPress log all authentication events and have fail2ban react on them.
Note: This guide is written for Ubuntu, but most of it should apply to other distributions as well.
Install fail2ban
It doesn’t get much easier than this:
$ apt-get update && apt-get install fail2ban
Now, fail2ban works right away detecting any failed login attempts on SSH. The default settings will make 5 failed login attempts within 600 seconds to cause an IP ban via the iptables firewall for 600 seconds.
Logging Authentication Events
So, first of all we will make WordPress log all authentication events to the system authentication log file (the authlog, on Ubunutu found in /var/log/auth.log
). You might write your own plugin, but the WP fail2ban plugin does exactly this and have a few very useful features. I usually just download the single PHP file in the plugin into my mu-plugins
directory, but you may also install it as a regular plugin. Without any more fuzz, WordPress will now log all authentication events.
Note that if you are behind a reverse proxy, it is extremely important that you set the WP_FAIL2BAN_PROXIES
constant. Otherwise you will end up banning your proxy, blocking all incoming requests from everybody. Read the FAQ for info on this.
Setting up the Filter and Jail
First we have to set up the filter. It is really important that the regexes in the filter matches the log entry format from WordPress. Luckily a filter is also provided with the WP fail2ban plugin, so we can just download that:
$ sudo curl https://plugins.svn.wordpress.org/wp-fail2ban/trunk/filters.d/wordpress-hard.conf > /etc/fail2ban/filter.d/wordpress.conf
Feel free to inspect the file so you understand what it does.
(Oh, and btw: Since I first wrote this post, the plugin also includes a “soft” filter. You should check it out.)
Now, we’ll set up the jail. Create the file /etc/fail2ban/jail.d/wordpress.conf
with the following content:
[wordpress]
enabled = true
filter = wordpress
logpath = /var/log/auth.log
port = http,https
Restart fail2ban and you should be all set:
$ service fail2ban restart
To make sure it is working, try a few bogus logins to WordPress. You should see entries like this appended to your /var/log/auth.log
:
Nov 7 18:58:27 localusername wordpress(example.com)[12267]: Authentication failure for footer from 123.45.67.89
Do that more that 5 times, and you should see something like this in your /var/log/fail2ban.log file:
2014-11-07 19:01:35,640 fail2ban.actions: WARNING [wordpress] Ban 123.45.67.89
All connections from 123.45.67.89 should now be stopped in iptables. Note that the default iptables-multiport
action won’t drop already established TCP connections, so you might still receive requests from your test browser if keepalive is enabled. If you want to make sure all established connections to the offending IP is killed, you have to create your own custom action and include a tool like tcpkill
or simply use the provided route
action.
Customizing the Jail
Inspect /etc/fail2ban/jail.conf
to see what the default settings are, but ever edit this file directly. Do your changes in the jail.local
file (you’ll have to create it) or files in the jail.d
folder like we did above.
The most important options that you might want to tinker with are these, which you should add to your /etc/fail2ban/jail.d/wordpress.conf
file:
maxretry = 3
findtime = 10800
bantime = 86400
Tune these settings to your liking. What my suggestion here does is: If 3 failed login attempts from the same IP is found within 10800 seconds (3 hours), the IP will be banned for 86400 seconds (24 hours).
Optional: Changing the Default Action
The default action is iptables-multiport
. I like to use the route
action instead. It is extremely fast both for a large number of IPs and since it blocks traffic even before it hits iptables. It also stops any traffic on already established connections immediately.
To change the default action for your WordPress jail, find one you like in /etc/fail2ban/action.d
– like route
and add the following to the /etc/fail2ban/jail.d/wordpress.conf
file:
action = route
To inspect what bans are in place with route, issue the following command:
$ ip route list | grep unreachable
Optional: Custom Action: Using UFW
I like to keep things simple, so I like to use UFW (Uncomplicated FireWall) instead of iptables directly.
Since it is quite easy to create your own custom actions, setting up an action that use UFW is really easy. Just drop the following in /etc/fail2ban/action.d/ufw.conf
:
[Definition]
actionstart =
actionstop =
actioncheck =
actionban = ufw insert 1 deny from <ip>
actionunban = ufw delete deny from <ip>
I don’t think it needs an explanation. Now you can use action = ufw
in your jail.
Important Note on Reverse Proxies
If you’re using a reverse proxy, but it is on a different host than your WordPress installation – like a load balancer, banning on your WP host isn’t going to do anything, since all the traffic is coming from your reverse proxy. In that case, you will have to set up a custom action (like the previous chapter with UFW), using an API so you can ban the offending IP on the host where your reverse proxy is.
If you happen to be using a Rackspace Cloud LoadBalancer, I have the recipe for you:
Using fail2ban from behind a Rackspace Cloud LoadBalancer
If you’re using something else, you can still use it as a starting point, but you’ll have to provide your own API wrapper.
Caveats
No IPv6 support, but it’s coming. A patch with experimental support is available.
… there’s more
Now, if you haven’t already, go take a look at the FAQ for the WP fail2ban plugin. Because the plugin isn’ t restricted to logging successful and unsuccessful login attempts.
It can also:
- log pingbacks
- change the logfile
- block user enumeration attempts
- shortcut the login process if a specified username is provided
So go check it out now.
I came across your article while a few blogs on my virtual server were being hosed by an xmlrpc attack. Your article got me to thinking about how I could leverage fail2ban not just for logins, but for xmlrpc and wp-comments-post.
I wrote up an article on using fail2ban to do just that. I’d appreciate any feedback that you might have.
http://www.robotshavefeels.com/mitigating-wordpress-attacks-with-fail2ban/
Hi Bjørn,
Thank you for providing this! I ran into an issue where the server IP was actually getting banned and causing the server to appear offline so by adding
[wordpress]
enabled = true
filter = wordpress
logpath = /var/log/auth.log
port = http,https
ignoreip = 127.0.0.1/8 <————–
ignoreip = Server IP <————-
Seemed to of resolved the issue.
I think `ignoreip = 127.0.0.1/8 <————–` should be always allowed. Not from that block but globally.
Hello Bjørn,
please update the http://plugins.svn.wordpress.org/wp-fail2ban/trunk/wordpress.conf path in the article, it is now https://plugins.svn.wordpress.org/wp-fail2ban/trunk/wordpress-hard.conf
Thanks,
Martin
Thanks a lot, Martin! It’s updated now.
The correct link is now: https://plugins.svn.wordpress.org/wp-fail2ban/trunk/filters.d/wordpress-hard.conf
There was a missing “filter.d” subpath. Thank you for the article. This helped me a lot to ban those attackers from my site.
Updated now. Thanks a lot! :-)
Hi Bjørn,
After setting up the module and adding the fail2ban filter, I noticed that failed authentication attempts were not being picked up.
I discovered that adding the following line to the filter fixed this:
^%(__prefix_line)sAuthentication failure for .* from $
This picks up log entries like the following:
Jul 28 10:29:25 aperture wordpress(www.rehmetphoto.com)[20330]: Authentication failure for admin from 148.182.52.148
Oops, somehow in my previous comment, the \ part of the filter line got “filtered” out. It should be:
^%(__prefix_line)sAuthentication failure for .* from \$
Let’s hope this attempt works!
Damn!
WordPress is filtering out \ – escaping every character this time.
Thanks Bjorn,
I’ve looked at implementing this however I think the link to the repo is broken!?
Thanks,
Andy
Yeah, the plugin repo has changed a little, but you should be able to figure it out ;-)
Note that the `sudo` in this one does not help in writing to /etc. What you want to do is get the file in your home directory then `cp` it, or become root with `sudo su -` and then run the curl command. The `>` does not know that there is a sudo…
sudo curl https://plugins.svn.wordpress.org/wp-fail2ban/trunk/filters.d/wordpress-hard.conf > /etc/fail2ban/filter.d/wordpress.conf
curl https://plugins.svn.wordpress.org/wp-fail2ban/trunk/filters.d/wordpress-hard.conf | sudo tee -a /etc/fail2ban/filter.d/wordpress.conf
Look up the exact tee syntax for use case with man tee. The above would append the contents of the file to the wordpress.conf file.
Hi Bjorn, thanks for the clean guide. Im so far but it isn’t working, for CentOS 6 the log path should be /var/log/secure i think? But it isn’t getting logged. Any idea why?
I’m sorry, but I don’t know CentOS enough to help you. I’m an Ubuntu guy.
I have not been attacked heavily enough to affect performance but I have had issues with people enumerating usernames then spamming them. Also recently some idiot keeps requesting a password change on my site with my username. This plugin and the accompanying filters has largely put a stop to both. Thank you!
Thanks for the tutorial. I’m using the WP Fail2ban Redux and it is working great!
I see few IPs are now banned. Hopefully this can stop skiddies from attacking my blog.
I have a question. Will this work on a server where WordPress is not installed, but hackers are trying to find and log in to WordPress anyway? My logs are full of attempts to reach domainname.com/wp-admin, even though I’m not running WordPress.
No, but you can create a fail2ban filter that looks for those URLs in your access log.
Can’t seem to get this working. I have the plugin in the mu-plugin directory and seeing the logs in the auth.log, I’ve got the filters setup per instructions and the jail.d wordpress config. It shows unuauth attempts; however, not doing anything about it.
No working
Thank you for the useful comment.