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

Unlocking Efficiency and Productivity through Ruby on Rails Developers: Their Role

Unlock unprecedented efficiency and productivity with our expert Ruby on Rails developers. Leveraging the power of this robust framework, we deliver high-quality, scalable, and efficient web applications that drive your business forward. Partner with us to transform your web development journey and achieve your business goals faster.

Read More

What is Amazon Web Services For?

Amazon Web Services (AWS) is a comprehensive cloud computing platform provided by Amazon. It offers a wide range of services including computing power, database storage, content delivery, and other functionalities that help businesses scale and grow. AWS provides flexible, reliable, and scalable solutions for developers and businesses of all sizes.

Read More

Essential Software for Thriving Businesses: CRM, Manpower, and Inventory Management Systems

Equip your business with essential software to thrive in today's digital landscape. From productivity tools to data analytics, these software solutions drive efficiency, foster innovation, and fuel growth. Discover the must-have software for your business success in our comprehensive guide.

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.