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:

  1. Two-factor authentication on every administrator: defeats credential-stuffing entirely.
  2. Login URL change + IP rate limiting at the WAF level: kills the noise before it reaches WordPress.
  3. Disable XML-RPC unless you actively use it, closes the second-largest brute force surface.
  4. 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

  1. What is a WordPress brute force attack?
  2. How brute force has evolved in 2025–2026
  3. How to tell you’re being brute forced right now
  4. The 14-layer defense that actually works
  5. Exact wp-config and server configurations to deploy
  6. What to do if a brute force attack succeeded
  7. 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:

  1. 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.
  2. Inject SEO spam, malware, or backdoors to monetize the site directly (the pharma hack, Japanese SEO spam, and redirect hack families).
  3. 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:

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:

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

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:

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:

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:

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:

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

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.