Fail2ban + WordPress + Nginx

I’ve been using the Limit Login Attempts plugin for WordPress for quite a while. It basically logs failed login attempts and automatically blocks multiple attempts from a single IP address. A few days ago I’ve switched to fail2ban instead, which is pretty new to me.

Fail2ban with WordPress and Nginx

Fail2ban is a fairly simple yet very flexible framework that monitors log files for certain patterns, and runs preconfigured actions upon certain events.

Out of the box fail2ban comes with many so called filters, which are sets of matching rules, for example SSH auth failure, vsftpd login failure and more. As well as predefined actions, like block the IP address via iptables, send an e-mail with the IP WHOIS info, etc.

I haven’t had too much time to play around with the configs, but I did manage to get it to work with my WordPress install on nginx, and here’s how.

I created a new security.php plugin in wp-content/mu-plugins with the following code (not really sure why Core doesn’t do this already):

function my_login_failed_403() {
    status_header( 403 );
add_action( 'wp_login_failed', 'my_login_failed_403' );

Basically the problem is that WordPress returns authentication failure with the status code of 200, which is the same status code of an authentication success, so there’s no easy way to tell the difference between the two just by looking at nginx’s access log.

The above code adds the 403 status code to failed login attempts, either via wp-login.php or xmlrpc.php.

Next, assuming you’ve already installed fail2ban (sudo apt-get install fail2ban) let’s define a new filter in /etc/fail2ban/filter.d and call it wordpress-auth.conf:

failregex = <HOST>.*POST.*(wp-login\.php|xmlrpc\.php).* 403 

If you look at other files in filter.d, you’ll know what’s going on. failregex is a regular expression that matches a POST request to wp-login.php or xmlrpc.php with a status code of 403 in nginx’s access logs, assuming you’re using the default format. Probably not the best and fastest regex in the world, but serves its purpose.

Finally, let’s create a jail for all those bruteforce script kiddies. Let’s append the following code to the end of /etc/fail2ban/jail.conf:


enabled  = true
port     = http,https
filter   = wordpress-auth
logpath  = /var/log/nginx/access.log
maxretry = 3
bantime  = 3600

This is pretty easy to understand. We’re going to use our new wordpress-auth filter against nginx’s access.log file (double check the path) and ban IPs for one hour upon three or more failed attempts. Then we’re gonna restart the fail2ban daemon and watch the logs:

$ sudo service fail2ban restart
$ sudo tail -f /var/log/fail2ban.log
2014-01-16 05:46:54,372 fail2ban.actions: WARNING [wordpress] Ban
2014-01-16 06:05:01,915 fail2ban.actions: WARNING [wordpress] Ban
2014-01-16 06:37:50,345 fail2ban.actions: WARNING [wordpress] Ban


It is also a good idea to enable fail2ban for monitoring SSH access, FTP and any other services you’re running on your server. Up until a few days ago, I haven’t even realized someone was trying to bruteforce my SSH password. Now I get e-mail alerts everyday.

How do you protect your WordPress install and your servers from unauthorized access and bruteforce attacks?

About the author

Konstantin Kovshenin

WordPress Core Contributor, ex-Automattician, public speaker and consultant, enjoying life in Moscow. I blog about tech, WordPress and DevOps.


  • It’s a nice approach to protect the WordPress login system, and some security plugins make a similar work using .htaccess files.

    Maybe changing the HTTP response code when a login attempt fails would be a nice improvement for WordPress core.

  • Oh, I use:

    anti-spam for all fields
    limit login attempts
    wordfence security

    Though I am too late, since my site suffered from a pharma hack
    I use wordfence to send the obscure links to Google
    Google’s cache though never seems to get cleared

    WordPress becomes almost a daily routine, that takes too much of my time


  • а я меняю url админки и порт ssh на нестандартные, как правило если человек видит страницу 404й ошибки , а робот получает 404 статус то сразу перестают брутить, а что до ssh то просто об этом не заботился после того как порт сменил) пора видимо уже на nginx переезжать

  • This works great. I spent the last few days troubleshooting why fail2ban had quit working and ended up reinstalling it.

    In the process I expanded the functionality of the program.

    This jail alone caught over 1430 attempts at brute forcing our WP login page.

    Thanks for the write up.

  • Wow, Great idea Konstantin. I was thinking recently about installing something for security reasons. Also would be nice if anyone suggest this kind of feature to be implemented in JetPack.

  • Nice solution!

    Btw, we already have a plugin for integration with fail2ban… .

    >> How do you protect your WordPress install and your servers from unauthorized access and bruteforce attacks?

    For servers: I would choose to close the port 22 by default, and then open it only when required! I rarely needed to login via SSH on my own production server!

    • Yeah, I saw that plugin, not sure I want to write WordPress login attempts to the syslog, maybe error_log() would have been a better choice :)

      So how exactly do you open port 22 when it’s closed?

    • So how exactly do you open port 22 when it’s closed?

      It’s host specific. Some hosts allow. Some don’t.

      Amazon EC2, Google Compute Engine, Linode are some hosts that allow this thing. AWS, for example, has setup firewall in such a way that can be controlled from the web interface (or from command line as well).

    • I would just go with another port to begin with. Something like 12109 etc and turn off Password Authentication.

  • Послушай, подскажи что можно сделать ещё на shared хостниге. Кто-то брутит все мои домены на вордпрессе. Спасибо за наводку – Limit Login Attempts. А что можно поставить в shared envoroment (cpanel)?

    • Если shared-хостинг, то Limit Login Attempts поможет. Если сильно атакуют, попросите хостинг-провайдера забанить по IP адресам.