Using fail2ban to block WordPress login attacks

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.

23 Comments

  1. 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.

    1. I think `ignoreip = 127.0.0.1/8 <————–` should be always allowed. Not from that block but globally.

  2. 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

  3. 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!

    1. Yeah, the plugin repo has changed a little, but you should be able to figure it out ;-)

  4. 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?

    1. I’m sorry, but I don’t know CentOS enough to help you. I’m an Ubuntu guy.

  5. 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!

  6. 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.

  7. 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.

    1. No, but you can create a fail2ban filter that looks for those URLs in your access log.

  8. 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.

Comments are closed.