Protecting Apache webservers from WordPress admin login dictionary attacks with fail2ban

A better solution has been posted here but I’ll leave this post up too.

A very popular webserver I administer has been getting more attention from the script kiddies, and the Apache access log has been filling up with repeated hits to “POST wp-login.php” as scripts cycle through dictionary attacks. So it was time to invest some time in better security. And fail2ban seemed to be the way to go.

Installation is simple on Ubuntu as it’s supplied as a package:

sudo apt-get update
sudo apt-get install fail2ban

By default under version 0.8.2, only sshd protection is auto enabled. I’ve also enabled the apache-noscript filter included in the package, and so far have just added an additional filter to block the repeated attempts on WordPress admin login. This needs to allow a valid admin login, possibly one or two genuine password mistypes, and the self-referential redirect after login.


# Fail2Ban configuration file
# Author: Tim Connors
# $Revision: 668 $


# Option:  failregex
# Notes.:  Regexp to catch Apache dictionary attacks on Wrodpress wp-login
# Values:  TEXT
failregex = <HOST>.*] "POST /wp-login.php

# Option:  ignoreregex
# Notes.:  regex to ignore. If this regex matches, the line is ignored.
# Values:  TEXT
ignoreregex =

And added to /etc/fail2ban jail.local (don’t forget to copy jail.conf to jail.local after installation)


enabled = true
port    = http,https
filter  = apache-wp-login
logpath = /var/log/apache*/*access.log
maxretry = 5
findtime = 120

This seems to work okay, but maybe the timings need a bit of tweaking after testing.

  1. Thanks – that worked a treat…. 🙂

  2. Fail2ban filter for Wordpress | David Goodwin - pingback on April 17, 2013 at 2:58 pm
  3. We found through testing that the script listed above counted every login attempt whether or not they were successful. When several users would log into a WordPress site from a single location they would all get locked out. After testing the issue we noticed that Apache was returning a 200 code on unsuccessful login attempts and a 302 code on successful attempts. We changed the script to this:

    failregex = .*] “POST /wp-login.php HTTP/.*” 200

    This change caused fail2ban to ignore the successful logins and count the unsuccessful ones. We will continue watching it but for now it looks correct! Thanks for setting this up!

  4. Hi, with regard to the comment about the failregex catching successful logins as well as unsuccessful ones, can you confirm what the correct failregex to use is?

    You say you’ve marked it as deprecated, but does this mean you’ve updated your main post content, or the version from Douglas should be used?

    Thanks for posting this is very helpful.

    • This jail monitors apache2/access.log so it picks up all POSTs to wp-login.php. So these are login attempts, not successes or failures.

      The page linked to here is a much better solution.

      This page has not been edited or updated in any way. The linked page is my preferred method of catching failed logins.

  5. Hi, cool man, I´m trying to do a filter to fail2ban block ips that tries to do brute force in apache2, but it didn´t work =/

    Can you help me?

    That is the log line:
    [Sun Sep 28 04:35:44 2014] [error] mod_qos(031): access denied, QS_SrvMaxConnPerIP rule: max=50, concurrent connections=70, c=

    And that is what I did:
    failregex = ^\[\w{1,3}.\w{1,3}.\d{1,2}.\d{1,2}:\d{1,2}:\d{1,2} \d{1,4}. \[error] \[mod_qos(031)].access denied, QS_SrvMaxConnPerIP rule: max=50, concurrent connections=*, \[c=]

    I just to identify the mod_qos … QS_SrvMaxConnPerIP and block the ip.

    What did I do worng? =/

  6. I would make a regex that is much simpler. Like, you don’t need to try to match the date at all and might be able to just match QS_SrvMaxConnPerIP (I know nothing of mod_qos).

    Use the helper app fail2ban-regex to test and tweak your regex against a partial log.

Leave a Comment

NOTE - You can use these HTML tags and attributes:
<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

Trackbacks and Pingbacks: