Troubleshooting UFW Firewall Rules: Why “deny from IP” Isn’t Blocking WordPress Brute Force Attacks


1 views

When dealing with persistent brute force attacks on WordPress login pages, many administrators turn to UFW (Uncomplicated Firewall) as their first line of defense. The typical approach is to block the offending IP address with a command like:

sudo ufw deny from xx.xx.xx.xx to any

However, as seen in the server logs, Apache continues to serve HTTP 200 responses to POST requests from the blocked IP. This indicates the firewall rule isn't working as expected.

The issue often stems from these overlooked aspects:

  • UFW rules are processed in order - earlier allow rules might take precedence
  • Apache might be listening on a different interface than UFW is protecting
  • Port-based rules may not properly filter application-layer attacks

Here are three approaches to properly block malicious traffic:

1. Verify Rule Ordering

Check your current rules with numbering:

sudo ufw status numbered

If your deny rule appears after port-specific allows, reinsert it at the top:

sudo ufw insert 1 deny from xx.xx.xx.xx

2. Combine UFW with Apache ModSecurity

For WordPress-specific protection, create a custom rule:

SecRule REMOTE_ADDR "@ipMatch xx.xx.xx.xx" "id:1001,phase:1,deny,status:403,msg:'Blocked IP'"

3. Use Fail2Ban for Dynamic Blocking

Configure a custom filter for WordPress login attempts:

[wordpress-auth]
enabled = true
filter = wordpress-auth
logpath = /var/log/apache2/access.log
maxretry = 3
findtime = 600
bantime = 86400

To verify if traffic is actually being blocked:

sudo tcpdump -i eth0 host xx.xx.xx.xx
sudo iptables -vL | grep xx.xx.xx.xx
sudo tail -f /var/log/ufw.log

For comprehensive protection, consider adding Cloudflare's firewall rules:

# Cloudflare WAF rule to block IP
curl -X POST "https://api.cloudflare.com/client/v4/zones/{zone_id}/firewall/access_rules/rules" \
-H "X-Auth-Email: user@example.com" \
-H "X-Auth-Key: api_key" \
-H "Content-Type: application/json" \
--data '{"mode":"block","configuration":{"target":"ip","value":"xx.xx.xx.xx"},"notes":"Block brute force attacker"}'

When monitoring Apache access logs during routine server maintenance, I noticed repeated POST requests to /wp-login.php from foreign IP xx.xx.xx.xx, all returning HTTP 200 statuses despite firewall rules. The attack pattern shows:

xx.xx.xx.xx - - [01/Jan/2023:14:32:45 +0000] "POST /wp-login.php HTTP/1.1" 200 1234 "-" "Mozilla/5.0"
xx.xx.xx.xx - - [01/Jan/2023:14:32:47 +0000] "POST /wp-login.php HTTP/1.1" 200 1234 "-" "Mozilla/5.0"

The correctly configured UFW rule appears in ufw status:

sudo ufw status numbered
Status: active

     To                         Action      From
     --                         ------      ----
[1]  Anywhere                   DENY        xx.xx.xx.xx

Yet traffic persists because:

  • UFW manages iptables, but rules may not cascade properly
  • Apache might be listening on an interface bypassing the firewall
  • Port-based rules take precedence over IP-based rules

Solution 1: Layer 7 Protection with Fail2Ban

sudo apt install fail2ban
sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local

Add to /etc/fail2ban/jail.local:

[wordpress]
enabled = true
filter = wordpress
logpath = /var/log/apache2/access.log
maxretry = 3
bantime = 86400

Solution 2: Direct iptables Manipulation

sudo iptables -I INPUT -p tcp --dport 80 -s xx.xx.xx.xx -j DROP
sudo iptables-save > /etc/iptables/rules.v4

Test connectivity from the attacker's perspective:

sudo tcpdump -ni eth0 host xx.xx.xx.xx and port 80

Should show SYN packets without ACK responses when blocked properly.

For comprehensive protection:

# Rename login page
function rename_login_page() {
    if($_SERVER['SCRIPT_NAME'] == '/wp-login.php' && $_SERVER['REQUEST_METHOD'] == 'POST') {
        header('HTTP/1.0 403 Forbidden');
        exit;
    }
}
add_action('init', 'rename_login_page');

Combine this with:

  • Two-factor authentication
  • Login attempt limiting
  • Web Application Firewall (WAF)