2026年11月13日
2026年11月13日
Limit Login Attempts in WordPress: Plugin, Code, Nginx and Apache
This article is a localized edition of the Japanese original. Read the Japanese original
WordPress core has no mechanism that counts failed logins and locks the visitor out for a while. Out of the box, an attacker can keep guessing passwords (a brute-force attack) as many times as they like, so it is worth adding a limit on login attempts.
This article covers the four methods below. Everything was checked as of November 2026.
| Method | Best for |
|---|---|
| Plugin (Limit Login Attempts Reloaded) | You want to configure everything in the admin screen. Enough for most sites |
| Your own mu-plugin | You don't want another plugin and prefer a small piece of code you understand |
Nginx limit_req |
You can edit the server config and want to throttle requests before they reach PHP |
| Apache IP restriction on the login screen | Your administrators connect from known IP addresses |
To protect accounts even if a password leaks, also consider two-factor authentication (How to set up two-factor authentication in WordPress).
Method 1: Use Limit Login Attempts Reloaded
This is a free plugin from wordpress.org. Its listing is now titled "Limit Login Attempts Security", but the plugin slug is still limit-login-attempts-reloaded (the latest version as of November 2026 is 3.4.2).
Installation
In the admin screen, go to Plugins → Add New Plugin, search for "Limit Login Attempts", install it and activate it. If you have WP-CLI, run:
wp plugin install limit-login-attempts-reloaded --activate
Settings screen and defaults
Once activated, a "Limit Login Attempts" item appears in the left admin menu (if you turn off the left menu item in its settings, it moves under Settings). In the "Local App" section of the "Settings" tab you can change the values below. The defaults were checked in the plugin's source code.
| Field | Meaning | Default |
|---|---|---|
| allowed retries | Failed attempts allowed before a lockout | 4 |
| minutes lockout | Length of one lockout | 20 minutes |
| lockouts increase lockout time to … hours | After this many lockouts, switch to a long lockout | 24 hours after 4 lockouts |
| hours until retries are reset | Time before the failure count is reset | 24 hours |
On the same screen, "Notify on lockout" lets you email the administrator when a lockout happens.
Keeping your own IP from being locked out
Add your IP address to the "Safelist" on the "Logs" tab and that IP will never be locked out. IPs on the "Denylist" cannot log in at all.
If a CDN such as Cloudflare or a reverse proxy sits in front of your site, the client address PHP sees (REMOTE_ADDR) is the proxy's IP, and every visitor is counted as the same IP. In "Trusted IP Origins" on the "Settings" tab, specify the server variable that holds the real client address (for example HTTP_CF_CONNECTING_IP or HTTP_X_FORWARDED_FOR). On a site without a proxy, setting these lets attackers forge the header and slip past the limit, so match the setting to your actual setup.
Removing a lockout
If another administrator can log in, the "Active lockouts" list on the "Logs" tab shows the blocked IPs, and "Unlock" removes a lockout.
If you locked yourself out, the plugin's FAQ suggests the following.
- Log in from a different IP, such as your phone's mobile connection or a VPN
- If that is not possible, use FTP or your host's file manager to temporarily rename the
wp-content/plugins/limit-login-attempts-reloadedfolder, which disables the plugin. Log in, rename the folder back, and add your IP to the Safelist
On a single site with WP-CLI, the list of locked-out IPs is stored in the limit_login_lockouts option (pairs of IP address and release time). Emptying it as shown below removes every lockout.
# Check the current lockouts
wp option get limit_login_lockouts --format=json
# Remove all lockouts
wp option update limit_login_lockouts '[]' --format=json
This storage format is an internal detail of the plugin, so normally use the admin screen instead.
Method 2: Write your own mu-plugin
This example adds a minimal limit without installing a plugin. The wp_login_failed action counts failures, and the authenticate filter decides whether to block.
On the authenticate filter, WordPress core checks the username and password at priority 20. Checking at priority 30 means a locked-out visitor cannot log in even with the correct password. If you counted failures inside authenticate instead, a correct password could still get through during a lockout, so counting is done separately in wp_login_failed.
Save it as wp-content/mu-plugins/login-limit.php (create the mu-plugins folder if it does not exist; files in it load automatically and cannot be deactivated from the admin screen).
<?php
/**
* Plugin Name: Login Limit (simple)
* Description: Block logins for a while after repeated failures from the same IP.
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
const MY_LOGIN_MAX_FAILURES = 5; // Failures before a lockout
const MY_LOGIN_LOCK_SECONDS = 1200; // Lockout length (seconds)
function my_login_limit_key() {
$ip = isset( $_SERVER['REMOTE_ADDR'] ) ? $_SERVER['REMOTE_ADDR'] : '';
return 'my_login_fail_' . md5( $ip );
}
// Count each failed login.
add_action( 'wp_login_failed', function () {
$key = my_login_limit_key();
$count = (int) get_transient( $key );
set_transient( $key, $count + 1, MY_LOGIN_LOCK_SECONDS );
} );
// Once the limit is reached, block the login even if the password is correct.
add_filter( 'authenticate', function ( $user, $username ) {
if ( '' === trim( (string) $username ) ) {
return $user;
}
if ( (int) get_transient( my_login_limit_key() ) >= MY_LOGIN_MAX_FAILURES ) {
return new WP_Error(
'too_many_retries',
'Too many failed login attempts. Please wait a while and try again.'
);
}
return $user;
}, 30, 2 );
How it behaves, and what to watch for:
- A login attempt during a lockout also counts as a failure and resets the remaining lockout time to 20 minutes.
- The count is stored in a transient (the
_transient_my_login_fail_…option, or the object cache). To lift a lockout, runwp transient delete my_login_fail_<md5>with the md5 of the IP, or clear them all withwp transient delete --all(this also deletes other transients, which are normally regenerated). - Behind a CDN or reverse proxy,
REMOTE_ADDRis the proxy's IP. In that case, have the server replace it with the real client address first (Nginx'sreal_ipmodule or Apache'smod_remoteip). Trusting forwarded headers directly in PHP lets attackers bypass the limit by forging them. - Logins through XML-RPC (
xmlrpc.php) also pass throughauthenticate, so they are limited too. If you don't use XML-RPC, turning it off also helps (How to disable XML-RPC in WordPress).
Method 3: Throttle with Nginx limit_req
If you can configure Nginx yourself, you can rate-limit only POST requests to wp-login.php (login submissions) per IP. Blocked requests never reach PHP, which also keeps server load down.
Add the following to the http block (/etc/nginx/nginx.conf or a file in conf.d/*.conf). The map leaves the key empty for GET requests. limit_req_zone does not count requests with an empty key, so simply viewing the login screen is not limited.
map $request_method $wp_login_limit_key {
POST $binary_remote_addr;
default "";
}
# Up to 6 per minute per IP (one every 10 seconds on average)
limit_req_zone $wp_login_limit_key zone=wp_login:10m rate=6r/m;
Add a location for wp-login.php to your site's server block. For the part that passes requests to PHP, copy what is already in your location ~ \.php$ block (the include and fastcgi_pass lines below are an example for PHP 8.3 on Debian / Ubuntu).
location = /wp-login.php {
limit_req zone=wp_login burst=5 nodelay;
limit_req_status 429;
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php8.3-fpm.sock;
}
burst=5 nodelayaccepts up to 5 requests in a short burst without delay and returns 429 for anything beyond that.location = /wp-login.phpis an exact match, so it takes priority overlocation ~ \.php$.- After editing, check the syntax with
sudo nginx -tand then apply it withsudo systemctl reload nginx.
This only slows requests down; it does not lock anyone out after a number of failures. Use it together with Method 1 or Method 2.
Method 4: Restrict the login screen by IP in Apache
If your administrators connect from fixed IP addresses, allowing wp-login.php only from those IPs stops most attacks at the door, rather than counting attempts. In Apache 2.4, write the following at the top of the .htaccess file in your WordPress directory.
<Files "wp-login.php">
Require ip 203.0.113.10
Require ip 198.51.100.0/24
</Files>
- With several
Require iplines, a match on any one of them is allowed. All other requests get a 403. - The Apache 2.2 directives
Order deny,allow/Deny from all/Allow fromare legacy syntax in 2.4 (they only work withmod_access_compat). - To use
Requirein.htaccess, the server must allowAllowOverride AuthConfig(orAll). - This does not work for sites where people other than administrators log in, such as membership sites. If your own IP changes, you will lock yourself out too.
For more detail, see How to restrict the WordPress login page by IP.
Things to keep in mind
- Where many people share one IP, such as an office or school network, one person's failures can lock everyone out. Don't set the allowed failures too low.
- A limit only slows down brute-force attacks. Combine it with hard-to-guess passwords and two-factor authentication.
- Running the plugin and your own code together double-counts failures and locks people out sooner than expected. Pick one.
Summary
- The quickest start: install Limit Login Attempts Reloaded, add your IP to the Safelist, and set Trusted IP Origins if you use a CDN.
- If you'd rather not add a plugin, an mu-plugin that counts in
wp_login_failedand blocks inauthenticate(priority 30) does the same job. - If you can edit the server config, Nginx
limit_reqor ApacheRequire ipfilters requests before they reach PHP, which makes the protection more effective.
Questions about WordPress? The publisher of this site is based in Japan and replies in Japanese. Contact (Japanese)