đ 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:.htaccessrule) 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
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
.htaccessfile:<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:
- Sign up for Cloudflare and point your domainâs nameservers to them.
- Cloudflare now acts as a reverse proxy. All traffic to your site goes through their network first.
- 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.
đ Read the original article on TechResolve.blog
â Support my work
If this article helped you, you can buy me a coffee:

Top comments (0)