Block wordpress login attacks on csf

Introduction

This guide explains how to block WordPress login attacks on CSF, configuring ConfigServer Security & Firewall to automatically detect and ban IP addresses that repeatedly fail to log in to wp-login.php. Brute-force login attempts against WordPress are extremely common — automated bots probe wp-login.php on virtually any publicly discoverable WordPress site around the clock, and without protection like this, your server logs will fill up with thousands of these attempts over time, some of which will eventually succeed against weak credentials if left unaddressed.

CSF (ConfigServer Security & Firewall) is a popular firewall and intrusion-detection suite used widely on CentOS and cPanel servers. Out of the box, it blocks common attacks like failed SSH and IMAP logins, but WordPress-specific login protection isn’t built in by default — it requires adding a custom detection rule, which is exactly what this guide walks through, including two steps that are easy to miss and will leave the rule silently non-functional if skipped.


Implementation

I. Prerequisites

Before you block WordPress login attacks on CSF, make sure you have:

  • A CentOS server
  • A web server (Apache or LiteSpeed) serving your WordPress site
  • A WordPress installation with a known access log location
  • CSF firewall already installed and running

II. How CSF and lfd Work Together

Before editing any files, it’s worth understanding the two components involved, since this guide touches both:

  • CSF is the firewall component — it manages the actual iptables (or firewalld) rules that allow or block traffic
  • lfd (Login Failure Daemon) is a separate background service that continuously watches log files for suspicious patterns — like repeated failed SSH logins — and instructs CSF to add a temporary or permanent block when a threshold is exceeded

Custom detection rules, like the one this guide adds for WordPress, work by teaching lfd a new pattern to watch for in a specified log file. This is why restarting only CSF itself isn’t enough to activate a new custom rule — lfd is the service that actually needs to reload the updated pattern, which Step VII covers in detail after a lot of guides skip it entirely.

III. Understand How the Detection Rule Works

Add the following block to CSF’s custom regex file. Open it with a text editor:

vi /usr/local/csf/bin/regex.custom.pm

Add this rule:

if (($globlogs{CUSTOM1_LOG}{$lgfile}) and ($line =~ /(\S+).*\] "POST \/wp-login\.php.*" 200/)) {
    return ("Failed WordPress login from","$1","wordpress","5","80,443","3600");
}

Note: If you’re copying this from an older source, double-check that any quotation marks are straight quotes ("), not curly/smart quotes (" "). Smart quotes are a common artifact of copying text out of a word processor or certain web pages, and Perl (which regex.custom.pm is written in) will fail to parse the file correctly if they slip in.

What the return values mean, in order: a description shown in block notifications, the captured IP address ($1), a label used to group this rule’s blocks, the failure count threshold (5 — meaning 5 matched failures trigger a block), the ports to block (80,443), and the block duration in seconds (3600 = 1 hour).

IV. Why This Rule Matches on HTTP Status 200

This is the part that confuses a lot of people at first glance, since a 200 status code normally means “success” — so why would it indicate a failed login? Here’s the actual mechanism:

  • When a WordPress login succeeds, WordPress issues an HTTP 302 redirect to wp-admin — the browser is sent somewhere else, not shown a rendered page directly
  • When a WordPress login fails, WordPress re-renders the login page itself, showing an error message — which returns a standard 200 OK status, since a full page was successfully served, just one containing a login error rather than a dashboard

So counterintuitively, a 200 response to a POST /wp-login.php request specifically indicates a failed attempt, while a 302 indicates success — which is exactly why the regex pattern above filters for 200 rather than 302. This detail is worth understanding rather than just copying blindly, since it’s the entire basis for how this rule distinguishes real failed logins from successful ones in the log.

V. Configure the Custom Log Path

Now tell lfd which log file to actually watch. Open CSF’s main configuration file:

vi /etc/csf/csf.conf

Find (or add) the CUSTOM1_LOG setting and point it at your actual WordPress site’s access log:

CUSTOM1_LOG = "/home/*/access-logs/*"

The correct path depends on your hosting setup. Common examples:

cPanel server (domain-specific log):

CUSTOM1_LOG = "/home/username/access-logs/domain.com"

Plain Apache server with combined logging:

CUSTOM1_LOG = "/var/log/apache2/access.log"

Important: The wildcard example from the original quick-reference version (/home/*/access-logs/*) works across all cPanel accounts on a server, but it’s broader than necessary if you’re only protecting one specific domain — pointing directly at the specific log file for the domain you’re protecting reduces the log-scanning overhead and avoids accidentally matching unrelated sites’ login attempts against the same threshold count.

VI. Restart Both CSF and lfd

This is the step most commonly missed, and it’s the reason a correctly written rule sometimes appears to do nothing. Restarting csf alone reloads firewall rules, but lfd — the actual process reading log files and applying custom regex rules — needs to be restarted separately to pick up the new pattern and log path:

csf -r
systemctl restart lfd

If lfd isn’t running as a systemd service on your specific CentOS version, use service lfd restart instead.

VII. Verify the Rule Is Active

Confirm both services restarted cleanly:

systemctl status lfd
csf -l

csf -l lists currently active firewall rules; while this won’t show the custom regex rule directly (since no IP has been blocked yet), it confirms CSF itself is running correctly before you move on to testing.

VIII. Test the Rule

From a separate machine (not one you rely on for legitimate site access, in case something goes wrong), intentionally fail to log in to wp-login.php on your WordPress site five times in a row — matching the threshold set in Step III.

Then check whether the IP was blocked:

csf -g <TEST_IP_ADDRESS>

This searches CSF’s active rules for the specified IP. If it’s found, the rule is working correctly. You can also check lfd‘s own log for confirmation:

tail -f /var/log/lfd.log

Watching this file live while triggering test failures is the most direct way to confirm the entire pipeline — log line, regex match, and block — is functioning end to end.

IX. Whitelist Your Own IP First

Before relying on this in production, whitelist your own administrative IP address to avoid accidentally locking yourself out during legitimate troubleshooting or repeated password attempts:

csf -a <YOUR_IP_ADDRESS> "Admin - always allow"

Important: Do this before extensive testing, not after. It’s a genuinely common mistake to test a new brute-force protection rule from your own IP, get blocked by your own rule, and then be unable to reach the server’s control panel or SSH to fix it — plan around this in advance.

X. Unban an IP (If Needed)

If a legitimate user or your own IP gets blocked unexpectedly, remove the block with:

csf -dr <IP_ADDRESS>

XI. Troubleshooting Common Issues

The rule doesn’t trigger even after repeated failed logins: This is almost always one of two things: lfd wasn’t restarted after the configuration change (Step VI), or CUSTOM1_LOG doesn’t actually point to a log file that’s receiving WordPress access log entries. Confirm the log path is correct by checking it directly: tail -f /path/to/your/access-log while triggering a test failure, and confirm you see matching POST /wp-login.php lines with a 200 status.

Legitimate users are getting blocked too easily: Consider raising the threshold from 5 to a higher number in the regex rule’s return statement, especially if your users are prone to typos, or pair this with a WordPress-side login limiting plugin that shows a warning before CSF’s threshold is reached.

The rule blocks successful logins too: Double-check that the regex pattern specifically matches 200, not a broader pattern that could also catch 302. If you modified the original rule, verify the status code condition wasn’t accidentally altered or removed.

XII. Monitoring Blocked IPs Over Time

Once the rule is active, it’s worth periodically reviewing what it’s actually catching, both to confirm it’s working and to spot patterns worth acting on further.

View all currently blocked IPs:

csf -l

Search specifically for WordPress-related blocks, using the label defined in the rule (wordpress):

grep wordpress /var/log/lfd.log

This shows a running history of every IP that triggered the rule, which can reveal useful patterns — for example, a large number of attempts from a specific country or hosting provider’s IP range might justify a broader block using CSF’s country-blocking feature (CC_DENY in csf.conf), rather than relying solely on the per-IP threshold to catch each attacker individually.

Check overall lfd activity and confirm the service remains healthy:

systemctl status lfd

If lfd has crashed or stopped for any reason, no custom rules — including this one — will be enforced until it’s restarted, so periodic verification that the service is actually running is a worthwhile habit, not just a one-time setup check.

XIII. Best Practices for WordPress Login Security

  • Combine this with a WordPress-side login limiter plugin (like Limit Login Attempts Reloaded) for defense in depth — CSF blocks at the network/firewall level, while a plugin can add account-level lockouts and user notifications, covering scenarios where an attacker rotates IP addresses faster than CSF’s threshold catches them
  • Consider renaming or restricting access to wp-login.php entirely for an additional layer, though be aware this can break some plugins or workflows that expect the default login path
  • Enable two-factor authentication on WordPress admin accounts, which meaningfully reduces the impact even if a brute-force attempt eventually succeeds against a weak password
  • Review /var/log/lfd.log periodically to understand attack patterns and confirm the rule continues functioning correctly over time, especially after any CSF or server updates

XIV. Conclusion

Blocking WordPress login attacks with CSF comes down to teaching lfd a custom pattern that correctly distinguishes failed logins (HTTP 200) from successful ones (HTTP 302), pointing it at the right access log file, and — critically — restarting lfd itself, not just CSF, for the new rule to actually take effect. With your own IP whitelisted first and the rule verified through a real test, this becomes a reliable, low-maintenance layer of protection against the automated brute-force attempts that virtually every public WordPress site experiences continuously.

For more on CSF’s configuration options and custom rule syntax, see the official CSF documentation.


Frequently Asked Questions

Why does my rule need lfd restarted, not just CSF? lfd is the separate background process responsible for actually reading log files and matching custom regex patterns — CSF itself only manages firewall rules. Restarting CSF alone reloads firewall state but doesn’t make lfd re-read the updated regex.custom.pm file or the new CUSTOM1_LOG path.

Does this work with LiteSpeed or Nginx instead of Apache? Yes, as long as the web server’s access log uses a similar combined log format containing the HTTP method, request path, and status code — just point CUSTOM1_LOG at that server’s actual access log path instead of Apache’s default location.

Will this block legitimate users who mistype their password a few times? It’s possible if they fail 5 times in a row within the tracking window, which is why whitelisting known-good IPs (Step IX) and considering a slightly higher threshold for sites with less security-conscious users are both worth considering based on your specific situation.

Is CSF’s custom rule approach better than using a WordPress security plugin instead? They solve the problem at different layers and work well together rather than being an either/or choice — CSF blocks at the firewall level before the request even fully reaches WordPress, reducing server load from repeated attempts, while a WordPress plugin can add account-specific protections and admin notifications that a firewall-level rule can’t provide on its own.


If you have any questions about this setup or run into an issue not covered here, feel free to reach out to us at Pheonix Solutions — we’re happy to help.

Related Articles:

How to Manage the CSF Firewall in WHM/cPanel

Implement IPSET on csf on cPanel | Pheonixsolutions

admin

Writes about Cloud & AWS at Pheonix Solutions.

Leave a Reply

Scroll to Top