🚀 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.

How I Stopped Brutal WordPress Attacks Using Fail2Ban on Ubuntu VPS

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

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

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

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 =

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

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

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

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

And add the regex:

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

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

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 - Lead Cloud Architect

Darian Vance

Lead Cloud Architect & DevOps Strategist

With over 12 years in system architecture and automation, Darian specializes in simplifying complex cloud infrastructures. An advocate for open-source solutions, he founded TechResolve to provide engineers with actionable, battle-tested troubleshooting guides and robust software alternatives.


🤖 Frequently Asked Questions

âť“ What are the primary targets for WordPress brute-force attacks?

Attackers primarily target `wp-login.php` for user authentication attempts and `xmlrpc.php` for remote connection APIs, both of which consume significant server resources during high-volume attacks.

âť“ How does Fail2Ban compare to a Web Application Firewall (WAF) like Cloudflare for WordPress security?

Fail2Ban operates on the server, blocking IPs after malicious requests are logged, reducing server load. A WAF like Cloudflare acts as a reverse proxy, filtering traffic *before* it reaches the server, offering a more robust and resource-efficient solution for large-scale or distributed attacks by offloading the security perimeter.

âť“ What is a common implementation pitfall when using a WAF for WordPress security?

A critical pitfall is failing to lock down your server’s firewall (e.g., `ufw`) to only accept traffic from the WAF provider’s IP ranges. This oversight allows attackers to bypass the WAF by directly targeting your server’s real IP address.

Leave a Reply

Discover more from TechResolve - SaaS Troubleshooting & Software Alternatives

Subscribe now to keep reading and get access to the full archive.

Continue reading