DEV Community

Cover image for Solved: How I Stopped Brutal WordPress Attacks Using Fail2Ban on Ubuntu VPS
Darian Vance
Darian Vance

Posted on • Originally published at wp.me

Solved: How I Stopped Brutal WordPress Attacks Using Fail2Ban on Ubuntu VPS

🚀 Executive Summary

TL;DR: WordPress sites often suffer from brutal brute-force attacks targeting wp-login.php and xmlrpc.php, leading to 100% CPU utilization. This guide details how to stop these attacks using Fail2Ban on an Ubuntu VPS, complemented by advanced Web Application Firewall (WAF) strategies with Cloudflare for layered security.

🎯 Key Takeaways

  • Fail2Ban can be installed on Ubuntu VPS to scan web server logs (Nginx/Apache) for repeated failed login attempts to wp-login.php and automatically ban offending IP addresses using iptables/ufw.
  • Specific Fail2Ban jails can be configured for xmlrpc.php with stricter rules (e.g., lower maxretry, longer bantime) or xmlrpc.php can be completely disabled at the webserver level (Nginx: location = /xmlrpc.php { deny all; }, Apache: .htaccess rule) if not actively used.
  • For ‘nuclear’ protection, a Web Application Firewall (WAF) and CDN like Cloudflare can filter malicious traffic before it reaches the server, using rules such as a ‘Managed Challenge’ for wp-login.php and ‘Block’ for xmlrpc.php.
  • A critical final step when using a WAF is to configure the server’s firewall (e.g., ufw) to only accept incoming traffic on ports 80 and 443 from the WAF provider’s official IP ranges, preventing direct server access and WAF bypass.

Stop relentless WordPress brute-force attacks on your Ubuntu VPS. A senior DevOps engineer shares battle-tested Fail2Ban configurations and WAF strategies to secure your server, from quick fixes to permanent, layered solutions.

I Saw My CPU Spike to 100% on a WordPress Site. Here’s How I Used Fail2Ban to End the Attack.

I got the PagerDuty alert at 2:17 AM. High CPU utilization on web-client-gamma-03, a small Ubuntu VPS running a single WordPress site for a marketing client. My first thought was a runaway cron job or a poorly optimized plugin, the usual suspects. But when I SSH’d in and ran htop, I saw dozens of php-fpm processes eating every last CPU cycle. A quick tail -f /var/log/nginx/access.log told the real story: a relentless, distributed flood of POST requests to wp-login.php and xmlrpc.php from hundreds of different IPs. It wasn’t a bug; it was a brute-force attack, and it was slowly strangling the server to death. We’ve all been there, and frankly, it’s one of the most tedious problems to deal with.

First, Why Does This Even Happen?

Before we jump into the fix, you need to understand the ‘why’. This isn’t a sophisticated vulnerability exploit. Attackers are abusing two legitimate, core WordPress files:

  • wp-login.php: The standard user login page.
  • xmlrpc.php: An API endpoint that allows remote connections. It’s used by things like the WordPress mobile app and the Jetpack plugin to communicate with your site.

The problem is that both of these endpoints are public and designed to process authentication attempts. Attackers use massive botnets to throw millions of common username/password combinations at them. Each attempt forces WordPress to spin up a PHP process and query the database. Multiply that by a few hundred requests per second, and your server’s resources are completely consumed. You’re not getting hacked; you’re getting knocked offline by sheer volume.

Solution 1: The Quick Fix – A Basic Fail2Ban Jail

Your first move is to stop the bleeding. This is where Fail2Ban comes in. It’s a simple but powerful tool that scans log files for malicious patterns (like repeated failed logins) and then automatically updates your firewall (iptables or ufw) to block the offending IP address for a set amount of time. It’s the digital bouncer for your server.

First, let’s get it installed on our Ubuntu box:

sudo apt update
sudo apt install fail2ban
Enter fullscreen mode Exit fullscreen mode

Out of the box, Fail2Ban doesn’t have a rule for WordPress logins. We need to create one. To avoid having our custom configurations overwritten during package updates, we’ll work with local files.

Step 1: Create a local jail configuration.

sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local
Enter fullscreen mode Exit fullscreen mode

Step 2: Define the WordPress attack “signature”.

We need to tell Fail2Ban what a failed login attempt looks like in our web server’s access log. Create a new filter file:

sudo nano /etc/fail2ban/filter.d/wordpress-auth.conf
Enter fullscreen mode Exit fullscreen mode

Paste the following into that file. This regular expression looks for POST requests to wp-login.php that are logged in your Nginx or Apache access logs.

[Definition]
failregex = ^<HOST> .*POST .*wp-login\.php.*
ignoreregex =
Enter fullscreen mode Exit fullscreen mode

Pro Tip: The <HOST> placeholder is a Fail2Ban convention that automatically matches the IP address at the beginning of the log line. This regex is simple but effective for catching the most common attacks.

Step 3: Enable the new jail.

Now, open your jail.local file and add this block to the end. This tells Fail2Ban to use our new filter and what to do when it finds a match.

sudo nano /etc/fail2ban/jail.local
Enter fullscreen mode Exit fullscreen mode

Add this new jail definition:

[wordpress-auth]
enabled  = true
port     = http,https
filter   = wordpress-auth
logpath  = /var/log/nginx/access.log  # Or /var/log/apache2/access.log
maxretry = 3
findtime = 600
bantime  = 3600
Enter fullscreen mode Exit fullscreen mode

This configuration means: “If I see the same IP hit wp-login.php 3 times within 10 minutes (600s), ban it for 1 hour (3600s).”

Step 4: Restart and verify.

sudo systemctl restart fail2ban
sudo fail2ban-client status wordpress-auth
Enter fullscreen mode Exit fullscreen mode

You should see that the jail is active. Within minutes, you’ll start seeing banned IPs, and your CPU load will drop dramatically.

Solution 2: The “Smarter” Fix – Locking Down XML-RPC & Repeat Offenders

The first solution works, but sophisticated bots will just rotate through thousands of IPs. We can get smarter by adding more layers.

Targeting XML-RPC Attacks

The xmlrpc.php file is another huge target. Let’s create a specific, more aggressive filter for it.

Create the filter:

sudo nano /etc/fail2ban/filter.d/wordpress-xmlrpc.conf
Enter fullscreen mode Exit fullscreen mode

And add the regex:

[Definition]
failregex = ^<HOST> .*POST .*xmlrpc\.php.*
ignoreregex =
Enter fullscreen mode Exit fullscreen mode

Enable the new jail in jail.local:

[wordpress-xmlrpc]
enabled  = true
port     = http,https
filter   = wordpress-xmlrpc
logpath  = /var/log/nginx/access.log
maxretry = 2
findtime = 300
bantime  = 86400
Enter fullscreen mode Exit fullscreen mode

Notice we’re being more aggressive here: only 2 attempts are allowed, and the ban time is a full day (86400s). Legitimate use of XML-RPC is rare for most sites, so we can be stricter.

War Story Warning: Before you do this, ask your user or client if they use the WordPress mobile app or any third-party tool that relies on XML-RPC. If they don’t, you’re better off disabling it completely at the webserver level. It’s a cleaner, more efficient solution as the request never even hits PHP.

For Nginx: Add this to your server block: location = /xmlrpc.php { deny all; }

For Apache: Add this to your .htaccess file: <Files xmlrpc.php>Order allow,deny\nDeny from all</Files>

Solution 3: The ‘Nuclear’ Option – Stop Traffic Before It Hits You

Sometimes, the attack is so large and distributed that even Fail2Ban can’t keep up. Remember, Fail2Ban still requires your server to process the malicious request before it can block it. When you’re under a massive DDoS, that’s still too much work. The real pro move is to block the traffic *before it ever reaches your server*.

This is where a Web Application Firewall (WAF) and CDN service like Cloudflare comes in. The free tier is often more than enough to stop these attacks cold.

The strategy is simple:

  1. Sign up for Cloudflare and point your domain’s nameservers to them.
  2. Cloudflare now acts as a reverse proxy. All traffic to your site goes through their network first.
  3. Now you can use their powerful firewall tools to filter out bad traffic.

My Go-To Cloudflare Rules for WordPress:

In the Cloudflare dashboard, under “Security” -> “WAF”, create these firewall rules:

Rule Name Rule Logic Action
Challenge WP Login Page (http.request.uri.path contains “/wp-login.php”) Managed Challenge
Block XML-RPC (http.request.uri.path contains “/xmlrpc.php”) Block
Geo-Block High-Risk Countries (ip.geoip.country in {“CN” “RU” “IR”}) and (http.request.uri.path contains “/wp-admin/”) Block

The “Managed Challenge” rule is brilliant. Instead of blocking access to the login page outright, it presents a non-intrusive challenge (like the ones you see all the time) that bots can’t solve but humans barely notice. This single rule neuters 99% of automated login attacks.

Critical Final Step: If you use a WAF, you MUST lock down your server to only accept traffic from Cloudflare’s IPs. Otherwise, attackers can find your server’s real IP address and bypass the WAF entirely. Use your firewall (like ufw) to deny all incoming traffic on ports 80 and 443 except from Cloudflare’s IP Ranges. This is the step everyone forgets.

Wrapping Up

Dealing with brute-force attacks is a layered process. Start with Fail2Ban on your server—it’s essential for any internet-facing machine. But for high-value sites or when facing a persistent attack, offloading that security perimeter to a dedicated service like Cloudflare is the most robust and resource-efficient solution. Don’t let your server waste its precious CPU cycles fighting bots; let someone else do it for you.


Darian Vance

👉 Read the original article on TechResolve.blog


☕ Support my work

If this article helped you, you can buy me a coffee:

👉 https://buymeacoffee.com/darianvance

Top comments (0)