Layered WordPress Defense with Fail2Ban, NGINX, and Real Client IPs
Dots pattern

Layered WordPress Defense with Fail2Ban, NGINX, and Real Client IPs

Fail2Ban, the Swiss army knife of quick rules to block aggressive intruders. As part of a layered defense, fail2ban is quick and easy to deploy to stop aggressive attackers from compromising your site with automated and brute force approaches.

In this blog we are going to give you a quick recipe for making a defense on WordPress sites you can easily deploy whether you are behind a load balancer or directly connected to the internet.

Some notes on how we host WordPress websites

In our setup, we have a mix of deployment strategies. However, the stack on the machine is generally always the same. This is Varnish → NGINX → PHP-FPM.

For staging and testing websites, we have a separate server that has HAProxy sitting infront and the server is NAT’d. This allows us to spawn new servers and move them around quite quickly for testing needs. We also do this for production websites that require HA.

So when writing a Fail2Ban rule, we need to

  1. Block the real IP of the attacker
  2. Not block ourselves (obviously, but we’ve all done it)
  3. Make sure they are blocked even when we are NAT’d

See where your traffic is coming from

The following is a quick way to see if a ban will work at IPtables or you need our NGINX rules.

sudo ss -tnp | grep ':443' | head -n 20
FIN-WAIT-2 0      0               <machine>:53758           <external-ip>:443

If the above is only your gateway, then your IPTables rule likely won’t help. We do it anyway though. If you are getting a collection of public IP’s in here, then you are directly connected to the internet and IPTables is the best choice.

Install Fail2ban

sudo apt install fail2ban

As easy as that.

Fail2Ban WordPress NGINX Action

This is our custom action for banning in an NGINX

[Definition]
actionstart =
actionstop =
actioncheck =
actionban = echo "deny <ip>;" >> /etc/nginx/fail2ban/wordpress.conf && nginx -t && nginx -s reload
actionunban = sed -i '\#deny <ip>;#d' /etc/nginx/fail2ban/wordpress.conf && nginx -t && nginx -s reload

It requires the following in your /etc/nginx/nginx.conf inside the http { block.

...

http {
  include /etc/nginx/fail2ban/*.conf;

...

This will be where Fail2Ban adds/removes rules.

You will need to make sure the fail2ban folder exists.

Once done you can reload with

nginx -s reload

Fail2Ban Filters

Filters are the rules that tell Fail2Ban how to detect a threat from a log file or some other form.

Our filters use the GELF format, but you can adapt them to standard NGINX log rules like below. We also need the real IP address, so we use the following rules to get a JSON log and set the real IP recursively. You should only do ones that are applicable to your network!

  set_real_ip_from  127.0.0.1;
  set_real_ip_from  10.0.0.0/8;
  set_real_ip_from  172.16.0.0/12;
  set_real_ip_from  192.168.0.0/16;
  real_ip_header    X-Forwarded-For;
  real_ip_recursive on;
  
  log_format gelf_json escape=json '{ "timestamp": "$time_iso8601", '
     '"real_ip":"$real_client_ip", '
     '"remote_addr": "$remote_addr", '
     '"connection": "$connection", '
     '"connection_requests": $connection_requests, '
     '"pipe": "$pipe", '
     '"body_bytes_sent": $body_bytes_sent, '
     '"request_length": $request_length, '
     '"request_time": $request_time, '
     '"response_status": $status, '
     '"request": "$request", '
     '"request_method": "$request_method", '
     '"host": "$host", '
     '"upstream_cache_status": "$upstream_cache_status", '
     '"upstream_addr": "$upstream_addr", '
     '"http_x_forwarded_for": "$http_x_forwarded_for", '
     '"http_referrer": "$http_referer", '
     '"http_user_agent": "$http_user_agent", '
     '"http_version": "$server_protocol", '
     '"remote_user": "$remote_user", '
     '"http_x_forwarded_proto": "$http_x_forwarded_proto", '
     '"upstream_response_time": "$upstream_response_time", '
     '"nginx_access": true,'
   '"environment": "transient" }';

XML-RPC Filter

/etc/fail2ban/filter.d/wordpress-xmlrpc.conf

Standard log

[Definition]
# Match POST requests to xmlrpc.php in standard nginx access logs

failregex =
    ^<HOST> .*"POST\s+/xmlrpc\.php\s+HTTP/.*"

ignoreregex =

or with GELF JSON

[Definition]
# Match POST requests to xmlrpc.php in nginx JSON logs

failregex =
    ^.*"remote_addr"\s*:\s*"<HOST>".*"request"\s*:\s*"POST\s+//xmlrpc\.php(?:\s|\\).*$
    ^.*"remote_addr"\s*:\s*"<HOST>".*"request"\s*:\s*"POST\s+/xmlrpc\.php(?:\s|\\).*$

ignoreregex =

WP-LOGIN Filter

/etc/fail2ban/filter.d/wordpress.conf

Standard log

[Definition]
# Match POST requests to wp-login.php in standard nginx access logs

failregex =
    ^<HOST> .*"POST\s+/wp-login\.php\s+HTTP/.*"

ignoreregex =

Or with GELF JSON

[Definition]
# Match POST requests to wp-login.php in nginx JSON logs

failregex = ^.*"remote_addr"\s*:\s*"<HOST>".*"request"\s*:\s*"POST\s+//wp-login\.php(?:\s|\\).*$

ignoreregex =

Fail2Ban Jail

This is where the actual banning happens.

You will need to adjust the log location depending how you log. We use independent site logs, but you may have one large one.

In these Jail’s we use both IPTables for banning and NGINX. Iptables is still useful for direct connections or future topology changes, but NGINX is the authoritative enforcement point when NAT’d.

XML-RPC Jail

[wordpress-xmlrpc-site]
enabled  = true
filter   = wordpress-xmlrpc
action = nginx-deny
         iptables-multiport[name=Wordpress, port="http,https"]
logpath  = /var/log/nginx/site_name.access.log

# XML-RPC abuse is usually brute-force or amplification
maxretry = 3
findtime = 300
bantime  = 86400

backend  = auto

# Never ban LB or localhost
ignoreip = 127.0.0.1/8 ::1 10.245.0.0/16

WP-LOGIN Jail

[wordpress-nginx-site]
enabled = true
filter = wordpress
action = nginx-deny
         iptables-multiport[name=Wordpress, port="http,https"]
logpath = /var/log/nginx/site_name.access.log
maxretry = 15
findtime = 600
bantime = 3600
backend = auto

# Never ban LB or localhost
ignoreip = 127.0.0.1/8 ::1 10.245.0.0/16

Conlusion

Now you will see anyone brute forcing those endpoints banned from your site. Well done!

Related articles

WordPress Updates, How Do They Work?

Stay ahead of the curve with our WordPress update services. We ensure your website is always running the latest version, benefiting from new features, improved performance, and crucial security patches. Don't let outdated software hold you back. Choose our WordPress update services for a smooth, secure, and enhanced website experience

Read More

The importance of updating and maintaining Ruby on Rails applications

Boost your Ruby on Rails application's performance, security, and efficiency with our gem upgrades. Experience seamless integration, improved functionality, and the latest features that keep your app ahead of the curve. Upgrade your gems today and unlock the full potential of your Rails application!

Read More

Why Do You Need Website Hosting

A fast hosting provider is crucial for your website's success. It ensures quick loading times, enhancing user experience and SEO rankings. Fast hosting also supports high traffic volumes, ensuring your site remains accessible and performs well even during peak times. Choose a fast hosting provider for a smooth, efficient, and user-friendly website.

Read More
Get In Touch

Why partner with Digitize?

At Digitize, we are a focused team that invest constantly in improving so that we can provide the best capabilities to our clients. Our processes and teams are built around being flexible so we can deliver tailored solutions instead of trying to make existing solutions fit.

Each client has a dedicated account manager that will ensure you are always getting the best service possible.