Install
This is the repository archive. It installs, but the folder name will carry the branch suffix and updates will not flow.
Install straight from GitHub with WP-CLI:
wp plugin install https://github.com/kevin-terry/rate-limit-login/archive/refs/heads/main.zipRate Limit WordPress Logins
A lightweight WordPress must-use plugin that rate limits login attempts by IP address. No admin UI, no database tables, no bloat — just a single file using WordPress transients.
Why
WordPress doesn't limit login attempts by default. If your login endpoint is exposed, bots can hammer it indefinitely. Most security plugins that solve this come bundled with dozens of unrelated features.
This plugin does one thing: block an IP after too many failed login attempts. That's all folks.
How It Works
- Failed login attempts are tracked per IP. IPv6 addresses are grouped by their
/64prefix, since one user typically controls the whole block. - After 5 failed attempts within 10 minutes of the first failure, the IP is locked out for 15 minutes.
- The lockout applies to every login route —
wp-login.php, XML-RPC, and custom forms that callwp_signon(). - On
wp-login.php, locked IPs receive an HTTP429 Too Many Requestsresponse with aRetry-Afterheader. Other routes get a normal login error. - Only
POSTrequests are counted — visiting the login page doesn't trigger the limiter. - Attempts made during a lockout don't extend it.
- Successful login clears the counter for that IP.
With a persistent object cache (Redis, Memcached), the counter uses an atomic increment, so simultaneous requests can't undercount attempts, and the overhead is effectively zero. Without one, transients in the database are used; under heavy parallel traffic an attacker may get a few extra attempts before the lockout lands.
Installation
Composer (recommended):
composer require kevin-terry/rate-limit-login
The package type is wordpress-muplugin, so composer/installers places it in a subdirectory of your mu-plugins folder. WordPress doesn't load mu-plugins from subdirectories on its own — you need a loader such as the Bedrock autoloader.
Manual: copy rate-limit-login.php into wp-content/mu-plugins/. Create the directory if it doesn't exist.
wp-content/
└── mu-plugins/
└── rate-limit-login.php
Must-use plugins load automatically. No activation required.
Configuration
Override any of these by defining them in wp-config.php. There's no need to edit the plugin file, which would be overwritten on update.
| Constant | Default | Description |
|---|---|---|
RLL_MAX_ATTEMPTS |
5 |
Failed attempts allowed before lockout |
RLL_LOCKOUT_DURATION |
900 |
Lockout length in seconds (15 min) |
RLL_DECAY_DURATION |
600 |
Seconds from the first failure before the counter resets (10 min) |
RLL_IP_HEADER |
'' |
$_SERVER key holding the client IP behind a proxy (see below) |
define('RLL_MAX_ATTEMPTS', 3);
define('RLL_LOCKOUT_DURATION', 3600);
IP Detection
By default the plugin uses REMOTE_ADDR, which is the only reliable source when the site isn't behind a proxy.
If the site sits behind a CDN or reverse proxy, REMOTE_ADDR is the proxy's address, so every visitor would share a single counter. Set RLL_IP_HEADER to the header the proxy uses for the real client IP:
define('RLL_IP_HEADER', 'HTTP_CF_CONNECTING_IP'); // Cloudflare
define('RLL_IP_HEADER', 'HTTP_X_FORWARDED_FOR'); // most other proxies
If the header holds a comma-separated list, the last entry (the one the proxy itself added) is used. Invalid values fall back to REMOTE_ADDR.
Only set this when the site is reachable solely through the proxy. Otherwise anyone can send the header directly and pick whatever IP they like.
For anything more involved, filter the result:
add_filter('rll_client_ip', function (string $ip): string {
return $ip;
});
Testing
- Navigate to your login page.
- Submit incorrect credentials 5 times.
- On the 6th attempt you should receive a
429 Too Many Requestsresponse. - After 15 minutes (or the configured lockout duration), access is restored.
Compatibility
- WordPress 6.2+
- PHP 7.4+
- Works with standard and non-standard login paths (e.g. Bedrock, custom
/wordpress/subdirectory installs). - Compatible with NGINX Direct Delivery as long as PHP requests are processed by Apache.
License
GPL-2.0-or-later
Releases
1 release.
These releases are tags only. The author does not attach a packaged zip, so there are no download counts to report.