Server Administration

Using Fail2ban to Protect WordPress Servers From Brute Force and Bad Bots

Using Fail2ban to Protect WordPress Servers From Brute Force and Bad Bots

Security plugins do a good job of limiting login attempts on WordPress. But they all share a weakness: they run inside WordPress. Every malicious request still reaches PHP, loads WordPress and often queries the database before the plugin decides to block it. On a small VPS facing a determined botnet, the server can struggle even though no attack succeeds. That’s where Fail2ban for WordPress comes in.

Fail2ban takes a different approach. It watches your server’s log files, notices patterns of abuse and adds firewall rules that block the offending IP addresses completely. Once an IP is banned, its requests never reach the web server at all, let alone PHP. Using Fail2ban for WordPress is one of the most effective ways to reduce attack load on a self-managed server, and it works alongside your existing security plugin rather than replacing it.

This guide walks through installing Fail2ban, writing filters for common WordPress attacks and avoiding the mistakes that lock out legitimate users or do nothing at all.

How Fail2ban works

Fail2ban has three main pieces:

  • Filters are regular expressions that match suspicious lines in a log file, such as repeated POST requests to wp-login.php.
  • Jails connect a filter to a log file and define the rules: how many matches within what time window trigger a ban, and how long the ban lasts.
  • Actions define what happens when a ban is triggered, usually adding a firewall rule with iptables or nftables, and optionally sending a notification.

The process runs constantly, reading new log lines as they’re written. When an IP crosses the threshold, it’s banned for the configured time, then automatically unbanned.

Installing Fail2ban

On Ubuntu or Debian:

Never edit the main jail.conf file directly, because package updates overwrite it. Create local override files instead in /etc/fail2ban/jail.d/.

Start by protecting SSH, which most installations enable by default, and confirm Fail2ban is running:

Making sure your logs are useful

Fail2ban can only act on what’s in the logs. For WordPress, the web server access log is the main source. Check where yours lives, typically /var/log/nginx/access.log or a per-site log like /var/log/nginx/example.com.access.log.

The standard combined log format includes the client IP, request method, URL and status code, which is everything the filters need.

One important caveat: if your site is behind Cloudflare or another proxy, the access log shows the proxy’s IP unless you’ve configured the web server to log the real visitor IP. Without that, Fail2ban would ban Cloudflare’s addresses, potentially blocking large numbers of real visitors. Configure the real IP module first, then confirm the log shows actual visitor addresses.

Filter 1: WordPress login brute force

Create /etc/fail2ban/filter.d/wordpress-login.conf:

This matches any POST request to wp-login.php. A successful login also starts with a POST, but legitimate users log in a handful of times per day, while bots send dozens or hundreds of attempts. The jail’s threshold separates them.

Now create /etc/fail2ban/jail.d/wordpress.conf:

That bans any IP that sends more than six login POSTs within ten minutes, for one hour.

For more precision, some setups use a WordPress plugin that writes failed logins to the system authentication log, and Fail2ban reads that instead. This matches only genuine failures rather than all login attempts. Both approaches work. The access log method needs no plugin and catches traffic before WordPress is involved.

Filter 2: XML-RPC abuse

If you don’t block xmlrpc.php entirely, at least ban abusers. Create /etc/fail2ban/filter.d/wordpress-xmlrpc.conf:

And add a jail:

If you use Jetpack, which communicates through XML-RPC, add its IP ranges to the ignore list, covered below.

Filter 3: Vulnerability scanners

Bots constantly probe for known vulnerable files: old plugin paths, backup files, .env files, phpMyAdmin installations and shells left by other attackers. These requests usually return 404 errors. A burst of 404s for PHP files from one IP is a strong signal.

Create /etc/fail2ban/filter.d/wordpress-probes.conf:

failregex = ^<HOST> .* "(GETPOST) [^"]*\.(phpenvsqlbakzip)[^"]*" 404

And its jail:

Be careful with this one. A site with broken links can generate legitimate 404s. Keep the threshold reasonably high and the time window short, so only rapid scanning triggers it.

Testing filters before enabling them

Fail2ban includes a tool to test a filter against a real log file without banning anyone:

It reports how many lines matched and shows examples. If it matches nothing, the regex is wrong or the log format differs. If it matches far too much, tighten the pattern. Always run this before enabling a jail.

After enabling, reload and check status:

The status shows currently banned IPs and total bans.

Don’t ban yourself or your services

Add trusted addresses to the ignore list in a defaults file, such as /etc/fail2ban/jail.d/defaults.conf:

Include:

  • Your office or VPN IP
  • Monitoring services that check your site
  • Payment gateway webhook sources, if they could ever trigger a filter
  • Jetpack’s IP ranges if you use it and run the XML-RPC jail

If you do lock yourself out, you can unban from the server console or another connection:

Repeat offenders

The recidive jail bans IPs that keep getting banned by other jails, for much longer. It reads Fail2ban’s own log:

That bans an IP for a week if it was banned three times in a day. Persistent bots disappear quickly with this in place.

Fail2ban and Cloudflare

When traffic comes through Cloudflare, local firewall bans don’t stop the requests reaching Cloudflare, and even with real IPs logged, the connection to your server comes from Cloudflare’s addresses. A local iptables ban against the visitor IP therefore has no effect.

There are two solutions. You can use Fail2ban’s Cloudflare action, which calls the Cloudflare API to block the IP at Cloudflare’s edge. Or you can rely on Cloudflare’s own WAF and rate limiting for public traffic and use Fail2ban only for services that bypass Cloudflare, such as SSH.

Keeping an eye on it

Check Fail2ban occasionally:

  • Review jail status to see ban counts.
  • Look at fail2ban.log for errors, such as a log file that no longer exists after a configuration change.
  • After changing log formats or paths, retest the filters.

When configured carefully, Fail2ban for WordPress runs quietly in the background, removing thousands of junk requests a day and leaving your server’s resources for real visitors.

A practical rollout plan

Introduce Fail2ban gradually rather than enabling every jail at once. Start with SSH and the WordPress login jail, watch the ban counts and the logs for a week and confirm no legitimate users were blocked. Then add the XML-RPC jail if you keep that endpoint open, followed by the probe jail with a conservative threshold. Finally, add the recidive jail once the others are stable.

At each stage, ask the team whether anyone has had trouble reaching the site or logging in. Real-world feedback catches problems that log analysis can miss, such as a shared office connection where several staff members log in around the same time each morning and briefly look like an attack.

What the numbers usually show

On a typical small business VPS, the first week with these jails enabled often shows hundreds of banned IPs, most of them hitting wp-login.php and xmlrpc.php. CPU usage from PHP drops noticeably, especially overnight when bot traffic tends to peak. Error logs become shorter and easier to read. Those improvements are the real measure of success, more than the ban count itself.

Notifications

Fail2ban can email you when it bans an IP, using actions like sendmail-whois. For busy servers, that quickly becomes noise. A better approach is a daily summary, or simply checking jail status during routine maintenance. Reserve immediate notifications for unusual events, such as a ban on your SSH jail from an unfamiliar country, which might indicate a targeted attempt rather than background noise.

Combining with other layers

Fail2ban works best as one layer among several. At the edge, a CDN or firewall can stop large volumes of traffic before it reaches your server. On the server, Fail2ban catches what gets through and handles services the CDN doesn’t cover. Inside WordPress, security plugins, strong passwords and two-factor authentication protect accounts directly. Each layer catches things the others miss, and together they make brute-force attacks a minor background nuisance instead of a performance problem.

Keep the configuration in version control

Store your custom filters and jail files in a Git repository or your server documentation. When you build a new server, you can copy a tested configuration instead of rewriting regular expressions from memory.

Frequently asked questions

Do I still need a security plugin with Fail2ban?

Yes. Fail2ban handles volume at the network level. Security plugins provide malware scanning, two-factor authentication, file monitoring and other protections Fail2ban doesn’t offer.

Can Fail2ban run on shared hosting?

No. It needs root access to read logs and manage the firewall. It’s for VPS and dedicated servers.

How long should bans last?

An hour for login attempts is a good start, with a recidive jail for repeat offenders. Longer bans for scanners are reasonable, because legitimate visitors don’t probe for .env files.

Will Fail2ban slow down my server?

It uses very little CPU and memory for typical WordPress log volumes. On very busy servers, keep filters simple and point them at site-specific logs.