WP Manifestindependent plugin directory
manifest / security / rate-limit-login

Rate Limit Login

Lightweight login rate limiter using transients. Drop in wp-content/mu-plugins/.

★ 0stars
v1.0.4latest · 6mo ago

Install

Download source zip No release zip yet

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.zip
No built-in updater. New versions won't appear under Dashboard → Updates. Install new releases yourself, or let Git Updater track the repository.

Rate 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 /64 prefix, 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 call wp_signon().
  • On wp-login.php, locked IPs receive an HTTP 429 Too Many Requests response with a Retry-After header. Other routes get a normal login error.
  • Only POST requests 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

  1. Navigate to your login page.
  2. Submit incorrect credentials 5 times.
  3. On the 6th attempt you should receive a 429 Too Many Requests response.
  4. 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.

Tag
Published
v1.0.4 latest
Apr 15, 2026 6mo ago

These releases are tags only. The author does not attach a packaged zip, so there are no download counts to report.