Protect WordPress wp-login with nginx HTTP Auth + fail2ban

Protecting wp-login.php for WordPress is essential for brute force protection from hackers. Most WordPress administrators will use a plugin like All-in-One Security (recommended) or Wordfence to block users who are making excessive login attempts. The problem with the plugin technique is that these brute force protection methods are still expensive for your web server. When a user tries to log in, PHP processing occurs and MySQL queries are made to verify if the user is valid or not. If you are getting lots of failed login attempts then you will see CPU usage spike and RAM be unnecessarily used up.

Using a basic http authentication method with nginx is far less resource intensive than using a plugin. With the HTTP authorization popup no PHP or MySQL is required so your server will be using considerably less resources to protect itself against malicious attackers and hackers. We will configure fail2ban to scan the nginx log files and ban attackers automatically.

For this tutorial you will need shell access (root SSH access) to your web server running Debian or Ubuntu.

You must already have nginx, PHP and WordPress running to complete this guide.

Configure nginx with Basic HTTP Auth for WordPress

Install Apache utilities to get the .htpasswd file generator and fail2ban

sudo apt-get update
sudo apt-get install apache2-utils fail2ban -y

Create the .htpasswd file for a user called wpbullet

sudo htpasswd -c /etc/nginx/.htpasswd wpbullet

You will be asked for a password twice. This password will be md5 hashed and stored in the /etc/nginx/.htpasswd file

New password:
Re-type new password:
Adding password for user wpbullet

Have a look and you can see the .htpasswd file just contains a hashed version of your password

cat /etc/nginx/.htpasswd

Now we need to enable .htpasswd for nginx in the wp-login.php custom location, open your nginx virtual host

nano /etc/nginx/sites-available/wordpress

Make sure you have an nginx error log specified in your server block. fail2ban needs to scan error logs to find login failures.

Add the wp-login.php section, adjust your fastcgi_pass if you are still using PHP 5.

server {
        listen 80;
        server_name wp-bullet.com;
        access_log /var/log/nginx/wp-bullet.com.access.log;
        error_log /var/log/nginx/wp-bullet.com.error.log;

location = /wp-login.php {
        
        auth_basic "Restricted";
        auth_basic_user_file /etc/nginx/.htpasswd;
        include fastcgi_params;
        fastcgi_pass unix:/run/php/php7.0-fpm.sock;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        }

Ctrl+X, Y and Enter to Save and Exit.

Test the nginx configuration syntax is valid.

nginx -t

Restart nginx if you got not errors.

service nginx restart

Now we generate some log data so the nginx log is populated with http authorization failures. This will let us test if fail2ban will detect the login failures.

Make sure to use the right username and wrong password. Also use an incorrect user and password as each failure generates different log data.

Have a peek in your nginx log file

cat /var/log/nginx/wpbullet.error.log

Here we go.

2016/03/12 11:31:20 [error] 21062#21062: *1 user "wp" was not found in "/etc/nginx/.htpasswd", client: 192.168.60.1, server: 192.168.60.138, request: "GET /wp-login.php HTTP/1.1", host: "192.168.60.138"
2016/03/12 11:31:30 [error] 21062#21062: *1 user "bullet": password mismatch, client: 192.168.60.1, server: 192.168.60.138, request: "GET /wp-login.php HTTP/1.1", host: "192.168.60.138"

Now it's time to create the filter for nginx

Configure fail2ban to Ban WordPress Hackers

fail2ban uses filters to detect violations and jails to ban users who violate the filter.

Create fail2ban Filter for nginx HTTP Auth

Create then nginx filter, if you are on the latest fail2ban you may already have this file, in which case you can skip down to testing using the fail2ban-regex command.

sudo nano /etc/fail2ban/filter.d/nginx-http-auth.conf

Add this which is a regular expression match for the logs above adapted from here

[Definition]
failregex = ^ \[error\] \d+#\d+: \*\d+ user "\S+":? (password mismatch|was not found in ".*"), client: <HOST>, server: \S*, request: "\S+ \S+ HTTP/\d+\.\d+", host: "\S+"(, referrer: "\S+")?\s*$

ignoreregex = 

Ctrl+X, Y + Enter to Save and Exit.

Now we can test the nginx HTTP auth filter by scanning the error log specified in the nginx virtual host.

fail2ban-regex /var/log/nginx/wpbullet.error.log /etc/fail2ban/filter.d/nginx-http-auth.conf

You will see this output showing it found the login failures we generated before.

Running tests
=============

Use   failregex file : /etc/fail2ban/filter.d/nginx-http-auth.conf
Use         log file : /var/log/nginx/wp-bullet.error.log


Results
=======

Failregex: 2 total
|-  #) [# of hits] regular expression
|   1) [2] ^ \[error\] \d+#\d+: \*\d+ user "\S+":? (password mismatch|was not found in ".*"), client: , server: \S+, request: "GET /wp-login.php.*$
`-

Ignoreregex: 0 total

Date template hits:
|- [# of hits] date format
|  [2] Year/Month/Day Hour:Minute:Second
`-

Lines: 2 lines, 0 ignored, 2 matched, 0 missed

Create fail2ban Jail for nginx HTTP Auth

Make sure the you have a fail2ban jail folder

sudo mkdir -p /etc/fail2ban/jail.d

Create the fail2ban nginx http auth jail configuration file

sudo nano /etc/fail2ban/jail.d/nginx-http-auth.conf

Paste thie configuration which uses the filter we created before, scans all nginx log files and bans users for 6000 minutes who fail 3 times in a 60 second period.

[nginx-http-auth]
enabled = true
filter = nginx-http-auth
port = http,https
logpath = /var/log/nginx/*.log
findtime = 60
bantime = 6000
maxretry = 3

Now that we know have made the jail, test the fail2ban syntax so make sure it's all working

sudo fail2ban-client -d

If you didn't see any errors (warnings are OK) then we can restart fail2ban

service fail2ban restart

Checking the nginx HTTP Auth fail2ban Status

The fail2ban client can be used to show the statistics of its jails

sudo fail2ban-client status nginx-http-auth

During a local test I managed to get the gateway IP banned.

Status for the jail: nginx-http-auth
|- filter
|  |- File list:        /var/log/nginx/wp-bullet.error.log /var/log/nginx/error.log
|  |- Currently failed: 0
|  `- Total failed:     3
`- action
   |- Currently banned: 1
   |  `- IP list:       192.168.60.1
   `- Total banned:     1

You can also list the iptables

sudo iptables -L -n

This shows the iptables chain for limiting nginx HTTP Auth requests

Chain f2b-nginx-http-auth (2 references)
target     prot opt source               destination
REJECT     all  --  192.168.0.1          0.0.0.0/0            reject-with icmp-port-unreachable
RETURN     all  --  0.0.0.0/0            0.0.0.0/0
RETURN     all  --  0.0.0.0/0            0.0.0.0/0

You will see lots of bots scanning and putting default passwords in. They will quickly be banned. After I install this WordPress security solution I do not need any heavy PHP plugins like WordFence to lock users out. My web server can spend more time using its resources more wisely like serving content to you super fast.

Sources

Protect nginx with Fail2ban on Ubuntu
fail2ban nginx HTTP Auth filter
Viewing iptables from fail2ban