A WordPress brute force attack is an automated attempt to log into your site by guessing username and password combinations typically thousands per minute, sometimes millions per day across distributed botnets. The 2026 versions are smarter than the brute force you remember from 2015: they use leaked credentials from prior breaches, AI-generated password candidates, and slow-and-low attempts that evade rate limits.
The four protections that actually work in 2026:
- Two-factor authentication on every administrator: defeats credential-stuffing entirely.
- Login URL change + IP rate limiting at the WAF level: kills the noise before it reaches WordPress.
- Disable XML-RPC unless you actively use it, closes the second-largest brute force surface.
- Block compromised passwords on creation: the only way to ensure new accounts don’t ship with a known-bad password.
The protections that fail: CAPTCHA-only setups (modern AI solves them at scale), simple “block after 5 attempts” plugins (slow-and-low evades them), and “complex password rules” without breach-list checks. We’ll cover what to do, what to avoid, and exactly how to configure both in this guide.
Table of Contents
- What is a WordPress brute force attack?
- How brute force has evolved in 2025–2026
- How to tell you’re being brute forced right now
- The 14-layer defense that actually works
- Exact wp-config and server configurations to deploy
- What to do if a brute force attack succeeded
- FAQ
1. What is a WordPress Brute Force Attack?
A WordPress brute force attack is an automated process that submits username/password combinations to your login form (usually /wp-login.php or via XML-RPC) until one combination works. The attacker has three goals, in order of how often we see them:
- Resell access to access markets: compromised WordPress admin logins sell for $5–$80 each on dark-web marketplaces depending on the site’s traffic and Google PageRank.
- Inject SEO spam, malware, or backdoors to monetize the site directly (the pharma hack, Japanese SEO spam, and redirect hack families).
- Use the site as part of a botnet: relay spam email, host phishing pages, run DDoS attacks against other targets.
There are five subtypes of brute force you’ll see against a WordPress site, often simultaneously:
Subtype 1: Classic dictionary attack
Tries common passwords (admin, password, 123456, Welcome1, qwerty) against a list of known usernames (admin, administrator, the site’s domain name as a username, the names from the “About” page).
Subtype 2: Credential stuffing
Tries username/password pairs leaked from previous breaches (LinkedIn, Adobe, Dropbox, etc.). This is the most successful subtype because 65% of WordPress users reuse passwords across sites, per Verizon’s DBIR.
Subtype 3: Distributed brute force
Spreads the attempts across thousands of IP addresses (a botnet) so per-IP rate limits never trigger. From any one IP, you see 2–5 attempts per hour. Across all IPs, the site sees 50,000+ attempts per day.
Subtype 4: XML-RPC amplification
Sends a single XML-RPC request to /xmlrpc.php containing thousands of password attempts in one HTTP request using the system.multicall method. One request, one log entry, hundreds of password guesses.
Subtype 5: REST API enumeration + targeted attack
Uses the WordPress REST API (/wp-json/wp/v2/users) to enumerate the actual administrator usernames first, then runs a focused brute force against just those. Much faster and quieter than guessing usernames blindly.
Modern brute force campaigns combine all five. They enumerate users via REST, try credential-stuffing pairs against XML-RPC, and fall back to slow distributed dictionary attacks against /wp-login.php if that fails.
2. How Brute Force Has Evolved in 2025–2026
If your understanding of brute force ends at “blocks after 5 failed login attempts in 5 minutes,” your site is undefended against modern attacks. Three shifts changed the game.
Shift 1: AI-generated password candidates
Until ~2023, password guessing used static lists (RockYou, the Top 10K, etc.) or rules like “common word + 4 digits + special character.” In 2024–2026, attackers feed those lists into language models that generate personalized password candidates based on the target site’s content. A site selling skincare products gets passwords like Glow2024!, BrightSkin#1, and RetinolFan22! tried against the admin user. The hit rate against real human-chosen passwords climbed from ~3% to ~12%, four times more dangerous.
Shift 2: Slow-and-low evasion
Rate-limit-based defenses (“block IPs after 5 failed attempts in 5 minutes”) were the standard from 2014–2022. Modern brute force runs 2 attempts per IP per hour, distributed across 50,000+ IPs from a residential proxy network. Per-IP rate limits never trigger. The site sees 100,000+ attempts a day but no individual IP looks abusive.
Shift 3: Credential-stuffing-as-a-service
Five years ago, an attacker brute-forcing your site needed their own infrastructure. Today, services like Sentry MBA, OpenBullet, and several successor frameworks let any low-skill attacker rent a botnet, point it at a login URL, and run credential stuffing for $50–$200 per million attempts.
The economics of attacking small sites changed completely. Where attackers once focused on high-value targets, they now spray-attack every WordPress site on the internet because the unit cost is near zero.
The combined effect: the average WordPress site receives 43 brute force attempts per day, and roughly 0.4% of those succeed against unprotected sites. That’s a real 1-in-250 daily chance of being compromised by brute force alone. The attack is no longer a worry for “important” sites, it’s a worry for every site.
3. How to Tell You’re Being Brute Forced Right Now
Five signals to check.
1. Login attempt logs
If you have a security plugin already, it logs failed logins. Look for:
- More than 50 failed logins per day from any single source.
- Failed attempts against usernames you don’t have (
admin,root,webmaster, your domain as a username), pure indicators of automated guessing. - Failed attempts at unusual hours (3 AM local time).
If you have no plugin, search the access log:
# Failed login attempts (POST to wp-login.php with response 200 = wrong password)
grep "POST /wp-login.php" /var/log/nginx/access.log | grep " 200 " | wc -l
# Top IPs hitting wp-login.php
grep "POST /wp-login.php" /var/log/nginx/access.log \
| awk '{print $1}' | sort | uniq -c | sort -rn | head -20
# XML-RPC abuse
grep "xmlrpc.php" /var/log/nginx/access.log | wc -l
A normal small-business WordPress site sees 0–5 logins per day from 1–3 IPs. Anything above 50/day, or 10+ unique IPs, is being brute forced.
2. XML-RPC traffic
Most WordPress sites get zero legitimate XML-RPC traffic. If your access log shows requests to /xmlrpc.php, those are almost always attacks.
grep -c "xmlrpc.php" /var/log/nginx/access.log
Anything non-zero is suspicious. We see typical infected sites generating 5,000–50,000 XML-RPC hits per day.
3. Server resource spikes
Brute force eats CPU and memory. Symptoms:
- PHP-FPM or Apache hitting max child processes during random times.
- MySQL connection pool full.
- The site goes into 503 / “error establishing database connection” during the attack.
- Hosting provider sending CPU-overage warnings.
4. User enumeration probes
Check for requests to /?author=1, /?author=2, … or /wp-json/wp/v2/users from unfamiliar IPs:
grep -E "(\?author=[0-9]|/wp-json/wp/v2/users)" /var/log/nginx/access.log \
| awk '{print $1, $7}' | sort -u | head -50
These are reconnaissance, the attacker is mapping out which user IDs exist before launching the targeted brute force.
5. Password reset email floods
Some brute force frameworks try the password reset endpoint instead of the login endpoint. If your admin email starts receiving 20+ password reset emails per day, you’re on the receiving end.
4. The 14-layer Defense That Actually Works in 2026
No single defense is enough. Each layer narrows the attack surface; together they make brute force effectively impossible to succeed against.
Layer 1: Two-factor authentication on every administrator
Single most-effective defense. 2FA defeats credential stuffing entirely because the leaked password isn’t enough to log in. Use TOTP (Google Authenticator, Authy, 1Password) or a hardware key (YubiKey, Titan).
Skip SMS-based 2FA. SIM swapping is too easy in 2026; SMS adds almost no protection against a determined attacker.
Enforce 2FA at the role level, never let it be optional for administrators. If a non-technical admin objects to TOTP, give them a hardware key. There is no acceptable compromise.
Layer 2: Block compromised passwords on creation
A strong password is one that hasn’t appeared in a breach. Most “complex password rules” (!aA1 requirements) miss the point, Password123! meets every complexity rule and is in the top 100 leaked passwords.
Use a have-i-been-pwned API check on every password change:
// Add to your theme's functions.php or a small must-use plugin
add_action('user_profile_update_errors', function($errors, $update, $user) {
if (empty($user->user_pass)) return;
$hash = strtoupper(sha1($user->user_pass));
$prefix = substr($hash, 0, 5);
$suffix = substr($hash, 5);
$resp = wp_remote_get("https://api.pwnedpasswords.com/range/$prefix");
if (is_wp_error($resp)) return; // fail open if HIBP is down
$body = wp_remote_retrieve_body($resp);
if (stripos($body, $suffix) !== false) {
$errors->add('pwned_password',
'This password appears in a known data breach. Choose a different one.');
}
}, 10, 3);
This single check eliminates 70%+ of credential-stuffing successes.
Layer 3: Move the login URL
/wp-login.php is hit by automated tools that don’t even check whether it exists, they just spray. Moving the login URL doesn’t add real cryptographic security, but it removes you from automated target lists.
Configure at WordPress level (multiple plugins do this; if you have GuardianGaze it’s a checkbox), or at the web-server level:
# Nginx — rewrite a custom URL to wp-login.php, return 404 for direct hits
location = /wp-login.php {
return 404;
}
location = /my-secret-login {
rewrite ^.*$ /wp-login.php last;
}
Don’t pick something predictable like /admin or /secure. Use 12+ random characters. The point is to remove your site from blanket scans, not to serve as the only defense.
Layer 4: Disable XML-RPC unless you actually need it
The vast majority of WordPress sites built after 2018 don’t use XML-RPC for anything. The Jetpack plugin needs it; the official WordPress mobile app needs it; almost nothing else does.
# Nginx — block XML-RPC entirely
location = /xmlrpc.php {
deny all;
return 403;
}
Or via .htaccess:
<Files "xmlrpc.php">
Order Allow,Deny
Deny from all
</Files>
Or via WordPress filter (in functions.php or a must-use plugin):
add_filter('xmlrpc_enabled', '__return_false');
If you do use Jetpack, whitelist Jetpack’s IP ranges and block all other access to XML-RPC.
Layer 5: Disable user enumeration
Stops the REST API and ?author=N endpoints from leaking your usernames.
// Block ?author= URL enumeration
add_action('template_redirect', function() {
if (!is_admin() && isset($_GET['author']) && intval($_GET['author']) > 0) {
wp_redirect(home_url(), 301);
exit;
}
});
// Restrict REST API users endpoint to authenticated users
add_filter('rest_endpoints', function($endpoints) {
if (isset($endpoints['/wp/v2/users'])) {
unset($endpoints['/wp/v2/users']);
}
if (isset($endpoints['/wp/v2/users/(?P<id>[\d]+)'])) {
unset($endpoints['/wp/v2/users/(?P<id>[\d]+)']);
}
return $endpoints;
});
Layer 6: Username discipline
- Never use
admin,administrator,root, your domain name, or your real name as the WordPress username. - For each user, set a different
display_namethanuser_loginso post bylines don’t reveal the login name. - Audit existing users:
SELECT user_login, display_name FROM wp_users
WHERE user_login = display_name OR user_login IN ('admin', 'administrator', 'root');
Layer 7: IP rate limiting at the web-server / WAF level
Don’t trust a WordPress plugin to rate limit. By the time PHP runs, the attacker has already consumed CPU and database connections. Rate limit at Nginx or your WAF.
# Define a 10MB shared memory zone tracking 1 request per minute per IP
limit_req_zone $binary_remote_addr zone=wp_login:10m rate=3r/m;
# Apply to login endpoints
location = /wp-login.php {
limit_req zone=wp_login burst=5 nodelay;
# ...rest of your PHP-handling block
}
location = /xmlrpc.php {
limit_req zone=wp_login burst=2 nodelay;
}
location = /wp-json/wp/v2/users {
limit_req zone=wp_login burst=2 nodelay;
}
A legitimate human logs in once, occasionally retries. Three attempts per minute is well above human use and well below bot use.
Layer 8: Country / ASN blocking (where appropriate)
If your site serves a single country or region, blocking other countries at the WAF removes 80%+ of brute force traffic instantly. Most attacks originate from a small set of hosting providers and residential proxy networks; blocking the top abusive ASNs (DigitalOcean low-rep IPs, OVH free tiers, certain Russian/Eastern European hosting providers) catches a large share without affecting real visitors.
This is bluntly effective but requires care, don’t block countries where you have legitimate users. GuardianGaze tracks the top abusive ASNs and updates them daily so you don’t have to maintain the list.
Layer 9: Login form error and timing equalization
Standard WordPress login form leaks information. The error message tells the attacker whether the username exists (“Invalid password” vs. “Invalid username”), and the response time differs between cases. Both let the attacker enumerate users quickly.
// Always show the same error
add_filter('login_errors', function() {
return 'Invalid login credentials.';
});
// Equalize response time so timing attacks fail
add_action('wp_login_failed', function() {
sleep(rand(2, 4)); // 2-4 second delay
});
Layer 10: Monitor and alert on successful logins
Failed login alerts are noise; successful logins are the high-signal event. Email or Slack-alert on:
- Any successful admin login.
- Any successful login from an IP not seen before.
- Any successful login outside business hours.
add_action('wp_login', function($user_login, $user) {
if (!user_can($user, 'manage_options')) return;
$ip = $_SERVER['REMOTE_ADDR'];
$known_ips = get_option('gg_known_admin_ips', []);
if (!in_array($ip, $known_ips)) {
wp_mail(get_option('admin_email'),
'Admin login from new IP',
"User $user_login logged in from $ip at " . current_time('mysql'));
$known_ips[] = $ip;
update_option('gg_known_admin_ips', $known_ips);
}
}, 10, 2);
This single alert often catches a successful brute force within minutes, far before the attacker has time to install backdoors.
Layer 11: Force HTTPS and HSTS
A brute force attempt over HTTP (or via an attacker-controlled MITM) can capture the password directly. Force HTTPS with HSTS:
// In wp-config.php
define('FORCE_SSL_ADMIN', true);
# Nginx
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
Layer 12: Disable file editing in admin
If a brute force succeeds, the attacker’s first move is usually to edit a theme file via Appearance → Theme File Editor and drop a backdoor. Disabling that surface buys you time to react:
// In wp-config.php
define('DISALLOW_FILE_EDIT', true);
define('DISALLOW_FILE_MODS', true);
Layer 13: Application-layer WAF with virtual patching
Even with all of the above, novel attacks find their way through. A web application firewall sitting in front of WordPress (Cloudflare, the GuardianGaze edge layer, ModSecurity with the OWASP Core Rule Set) catches:
- Login attempts from known abusive IPs (real-time threat feeds).
- Pattern-matched bot signatures (most brute force tools have detectable HTTP fingerprints).
- Newly disclosed plugin auth-bypass vulnerabilities (virtual patching).
The WAF catches the first attempt from a new attacker, before they can complete a single login attempt to your site.
Layer 14: Audit logging
You can’t react to what you can’t see. An audit log records every login attempt, password change, user creation, and role escalation. Crucially, it must store logs off the server, if the attacker gets in and erases the log, on-server logs are useless. Send to:
- A dedicated logging service (Datadog, Splunk, BetterStack).
- Your hosting provider’s centralized logs (if available).
- Your security platform’s log endpoint (GuardianGaze does this by default).
5. Exact Wp-config and Server Configurations to Deploy
Copy-paste ready. Test on staging before deploying to production.
wp-config.php additions
// === Security hardening ===
define('FORCE_SSL_ADMIN', true);
define('DISALLOW_FILE_EDIT', true);
define('DISALLOW_FILE_MODS', true);
// Limit post revisions and autosave
define('WP_POST_REVISIONS', 5);
define('AUTOSAVE_INTERVAL', 300);
// Disable debug logs in production
define('WP_DEBUG', false);
define('WP_DEBUG_LOG', false);
define('WP_DEBUG_DISPLAY', false);
// Stronger session cookies
@ini_set('session.cookie_httponly', 1);
@ini_set('session.cookie_secure', 1);
@ini_set('session.use_only_cookies', 1);
@ini_set('session.cookie_samesite', 'Strict');
// Fresh salts (generate from https://api.wordpress.org/secret-key/1.1/salt/)
define('AUTH_KEY', 'paste-your-real-64-char-key-here');
define('SECURE_AUTH_KEY', 'paste-your-real-64-char-key-here');
define('LOGGED_IN_KEY', 'paste-your-real-64-char-key-here');
define('NONCE_KEY', 'paste-your-real-64-char-key-here');
define('AUTH_SALT', 'paste-your-real-64-char-key-here');
define('SECURE_AUTH_SALT', 'paste-your-real-64-char-key-here');
define('LOGGED_IN_SALT', 'paste-your-real-64-char-key-here');
define('NONCE_SALT', 'paste-your-real-64-char-key-here');
Nginx — login + XML-RPC + REST API protection
# Rate-limit zone (place in http {} block)
limit_req_zone $binary_remote_addr zone=wp_login:10m rate=3r/m;
limit_req_zone $binary_remote_addr zone=wp_general:10m rate=30r/s;
server {
# ... your existing config
# Block XML-RPC
location = /xmlrpc.php {
deny all;
return 403;
}
# Block REST API user enumeration to unauthenticated users
location = /wp-json/wp/v2/users {
deny all;
return 403;
}
# Rate limit the login URL
location = /wp-login.php {
limit_req zone=wp_login burst=5 nodelay;
include fastcgi_params;
fastcgi_pass unix:/run/php/php8.2-fpm.sock;
}
# Block PHP execution in uploads
location ~* /wp-content/uploads/.*\.php$ {
deny all;
return 403;
}
# Block direct access to sensitive files
location ~ /\.(htaccess|env|git) {
deny all;
}
# Security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
}
Apache .htaccess
# Block XML-RPC
<Files "xmlrpc.php">
Order Allow,Deny
Deny from all
</Files>
# Block PHP execution in uploads
<FilesMatch "\.(?i:php|phtml|php[0-9])$">
<If "%{REQUEST_URI} =~ m#^/wp-content/uploads/#">
Require all denied
</If>
</FilesMatch>
# Block sensitive files
<FilesMatch "^\.">
Order allow,deny
Deny from all
</FilesMatch>
# Security headers
Header always set X-Frame-Options "SAMEORIGIN"
Header always set X-Content-Type-Options "nosniff"
Header always set Referrer-Policy "strict-origin-when-cross-origin"
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
File permissions
# Directories: 755, files: 644, wp-config: 400
find /var/www/yoursite/ -type d -exec chmod 755 {} \;
find /var/www/yoursite/ -type f -exec chmod 644 {} \;
chmod 400 /var/www/yoursite/wp-config.php
If you want to walk through the rest of WordPress hardening, server-level, database, user management, file permissions, debugging configuration, see our Part 2 advanced implementation guide.
6. What to Do if a Brute Force Attack Succeeded
Move fast. Every minute the attacker is in the site, they’re installing backdoors that survive password changes.
Step 1: Force-logout everyone
# Generate fresh salts and replace the AUTH_KEY block in wp-config.php
curl https://api.wordpress.org/secret-key/1.1/salt/
This invalidates every session cookie immediately, including the attacker’s.
Step 2: Reset every user’s password
wp user list --field=ID | xargs -I {} wp user reset-password {}
Step 3: Audit administrators
SELECT u.ID, u.user_login, u.user_email, u.user_registered
FROM wp_users u
INNER JOIN wp_usermeta m ON u.ID = m.user_id
WHERE m.meta_key = 'wp_capabilities' AND m.meta_value LIKE '%administrator%'
ORDER BY u.user_registered DESC;
Any administrator created within the last 30 days that you don’t recognize: delete immediately.
Step 4: Look for backdoors
The first thing a successful brute-force attacker installs is a way back in even after you reset passwords. Check:
# Suspicious recently-modified PHP files
find /var/www/yoursite/ -name "*.php" -mtime -7 \
| xargs grep -lE "eval\s*\(|base64_decode|str_rot13|gzinflate" 2>/dev/null
# PHP files in uploads (always malicious)
find /var/www/yoursite/wp-content/uploads/ -name "*.php"
# mu-plugins (most installs should have nothing here)
ls -la /var/www/yoursite/wp-content/mu-plugins/
Quarantine and investigate every file these commands return. If you find any backdoor, treat the site as compromised, follow our malware removal protocols.
Step 5: Look for the four common post-breach payloads
A successful attacker typically installs one of:
- A redirect or pharma hack (redirect / pharma guides apply).
- An SEO spam injection (Japanese SEO spam and pharma cleanup applies).
- A web shell for ongoing access (covered in our malware removal protocols).
- A spam relay (look for abnormal outbound SMTP traffic in your hosting bandwidth dashboard).
Step 6: Implement all 14 layers above before bringing the site back
Don’t restore service until every layer in section 4 is in place. The same brute force that succeeded once will succeed again the same day.
7. Frequently Asked Questions
How many brute force attempts does a typical WordPress site receive?
The 2026 average is 43 attempts per day for a small site, scaling to thousands per day for sites with any meaningful traffic or PageRank. Distributed attacks rarely show as more than 2–5 attempts from any single IP per hour, but cumulatively can total 10,000+ per day.
Will a CAPTCHA stop brute force?
Modern CAPTCHA-solving services (CapSolver, 2Captcha, AntiCaptcha) solve reCAPTCHA v2 in 10–20 seconds at $1–3 per 1,000 solutions. CAPTCHA helps against the cheapest end of the attack market but doesn’t stop a determined attacker. Treat CAPTCHA as one layer, not the only layer.
Should I rename wp-login.php to a custom URL?
It’s worth doing, but it’s not real security, it’s obscurity. Treat it as removing yourself from blanket scan lists, not as protection. The real defenses are 2FA, breach-list password checks, and rate limiting.
Are “Limit Login Attempts” plugins enough?
They handle naive brute force (single-IP, fast attempts) but not distributed brute force or XML-RPC amplification. Use them in addition to WAF-level rate limiting, not as a replacement.
Should I disable XML-RPC?
Yes, unless you actively use Jetpack or the official WordPress mobile app. XML-RPC’s system.multicall method lets a single HTTP request contain hundreds of password attempts, it’s the single highest-value brute force amplifier on a default WordPress install.
My site keeps showing 503 errors during attacks. What’s happening?
The brute force traffic is exhausting PHP-FPM workers or MySQL connections before legitimate visitors can be served. Move the rate limiting to the web-server level (Nginx/Apache) so the attempts are rejected before reaching PHP. WAF-level rate limiting goes one step further and rejects them before reaching your server.
Can a brute force attack happen even with a strong password?
Yes. Credential stuffing uses passwords from prior breaches, not guesses. A 16-character random password you’ve never used elsewhere is brute-force-proof. The same 16-character password you also used on a forum that was breached in 2021 is not. Always pair strong passwords with breach-list checks (Layer 2 above) and 2FA.
Why does Wordfence show me thousands of blocked login attempts?
That’s mostly noise. Wordfence’s free tier blocks at the application level after attempts have already consumed PHP/MySQL resources. The metric is the number of attacks reaching your server, not the number being efficiently rejected. WAF-level blocking at the edge is more efficient because attacks never reach WordPress at all.
Is 2FA on every admin really mandatory in 2026?
For any site with administrative access that produces revenue, has user data, or has any audience: yes. The single most-effective control against credential stuffing is 2FA. There is no acceptable substitute. If a non-technical admin objects to TOTP, give them a hardware key. The alternative is taking the risk that one reused password leak ends your site’s history.
Continue Reading
- WordPress Security 2026: The Complete Defense Guide – Part 1: broader architectural picture.
- WordPress Security 2026: Part 2 – Advanced Implementation & Hardening: server-level hardening beyond brute force.
- Website Hacked? 17 Signs Your WordPress Site Is Compromised: early-warning signs.
- WordPress Malware Removal 2026: Complete Detection & Removal Protocols: if a brute force succeeded.
Stop the next brute force at the edge. GuardianGaze’s WAF, IP reputation feed, and 2FA enforcement together block the brute force traffic ordinary plugins miss, before it ever touches WordPress. Install the free plugin or view paid plans.