Brute-force attacks are one of the most common threats against WordPress websites. By default, WordPress allows unlimited login attempts—which means bots can repeatedly guess usernames and passwords until they get it right.
To protect your site, you can limit login attempts even without a plugin. This post walks you through a simple method using WordPress filters and transients.
Why Limit Login Attempts?
By default, WordPress doesn’t limit how many times someone can try to log in. This creates a serious vulnerability that can be exploited by bots and hackers trying thousands of username-password combinations in a short amount of time.
Limiting login attempts helps prevent brute-force attacks, protects your server resources, and adds an extra layer of defense with minimal effort.
The Code
Here’s a refined and flexible version of the login limiter. It includes support for proxy services like Cloudflare, logs failed attempts, and uses constants to make configuration easier.
add_filter('authenticate', function($user, $username, $password) {
// Get real visitor IP
$ip = $_SERVER['HTTP_CF_CONNECTING_IP']
?? $_SERVER['HTTP_X_FORWARDED_FOR']
?? $_SERVER['REMOTE_ADDR'];
$ip = preg_replace('/[^0-9a-fA-F.:]/', '', $ip);
$key = 'failed_login_' . md5($ip);
$attempts = (int) get_transient($key);
$max_attempts = 2;
$lockout_duration = 1 * MINUTE_IN_SECONDS;
// 🚫 Block before trying authentication
if ($attempts >= $max_attempts) {
return new WP_Error('too_many_attempts', __('Too many failed login attempts. Please try again later.'));
}
// Let WordPress handle authentication if no result yet
if ($user instanceof WP_User) {
return $user;
}
// Perform authentication
$user = wp_authenticate_username_password(null, $username, $password);
if (is_wp_error($user)) {
$attempts++;
set_transient($key, $attempts, $lockout_duration);
error_log("Login failed ($attempts): IP $ip, Username: $username, Time: " . date('Y-m-d H:i:s'));
} else {
delete_transient($key); // successful login clears the block
}
return $user;
}, 30, 3);
MINUTE_IN_SECONDS
is a built-in WordPress constant that equals60
. It makes your code more readable and lets you use expressions like10 * MINUTE_IN_SECONDS
instead of writing600
directly. You do not need to define or replace it—WordPress provides it out of the box.
What This Code Does
This function taps into the authenticate
filter before WordPress completes the login process. It tracks failed login attempts for each IP address using the transient
API, which stores data temporarily in the database.
If the user exceeds the configured limit (5 attempts in this case), they’re blocked for 10 minutes.
- Detects the real IP address, even behind a proxy or CDN
- Hashes the IP to create a safe transient key
- Logs every failed login to the PHP error log
- Resets the attempt count after successful login
How to Whitelist Your Own IP
If you’re testing this code or managing a site regularly, it’s a good idea to whitelist your own IP address. This ensures you won’t accidentally lock yourself out after multiple failed login attempts.
You can do this by adding a simple check at the beginning of the function to bypass the login limiter for your IP:
$whitelisted_ips = ['123.123.123.123', '111.111.111.111']; // Replace with your real IP(s)
if (in_array($ip, $whitelisted_ips)) {
return wp_authenticate_username_password(null, $username, $password);
}
Add this right after the IP detection and sanitization lines:
$ip = $_SERVER['HTTP_CF_CONNECTING_IP']
?? $_SERVER['HTTP_X_FORWARDED_FOR']
?? $_SERVER['REMOTE_ADDR'];
$ip = preg_replace('/[^0-9a-fA-F.:]/', '', $ip);
// ✅ Add this block here
$whitelisted_ips = ['123.123.123.123'];
if (in_array($ip, $whitelisted_ips)) {
return wp_authenticate_username_password(null, $username, $password);
}
You can add multiple IPs to the array if you access your site from different networks (like home and office). Just be sure to remove or update them if your IP changes frequently.
You can find your current public IP by visiting a site like whatismyipaddress.com.
Things to Consider
While this solution works well and keeps your WordPress site lightweight, you should be aware of a few limitations:
- Shared IP addresses: If several users share the same IP, one person’s failed logins may block everyone temporarily.
- Transients are temporary: They expire automatically, so there’s no need for cleanup, but they aren’t stored across object cache resets.
- No UI or log viewer: This solution is meant for developers. If you prefer dashboards and settings, a plugin might suit you better.
Final Thoughts
WordPress is powerful, but it doesn’t limit login attempts by default. With just a few lines of code, you can significantly improve your site’s defense against brute-force attacks—without adding another plugin to your stack.
Looking for more ways to protect your site? Don’t miss our full guide on hardening WordPress security for more tips and code examples.