Blog · Security

Inside a Brute-Force Campaign Against wp-login

If your WordPress site has been online for more than a few weeks, it has almost certainly been hit by a brute-force campaign against wp-login.php. These attacks are automated, constant, and largely indiscriminate. Understanding what they actually look like in your logs makes it much easier to decide which defenses are worth setting up, and which are just noise.

What the Logs Actually Show

A brute-force campaign rarely looks dramatic. It looks like a steady, repetitive drumbeat of POST requests, often from dozens or hundreds of different IP addresses in rotation, all hitting the same endpoint within seconds of each other. A trimmed excerpt from an access log during an active campaign might look like this:

203.0.113.44 - - "POST /wp-login.php HTTP/1.1" 200 1843
198.51.100.12 - - "POST /wp-login.php HTTP/1.1" 200 1843
203.0.113.201 - - "POST /wp-login.php HTTP/1.1" 200 1843
192.0.2.87 - - "POST /wp-login.php HTTP/1.1" 200 1843
198.51.100.230 - - "POST /wp-login.php HTTP/1.1" 200 1843

Note the identical response size on every line. That is the telltale sign: it is the same login-failure page being returned over and over, to different source IPs, testing a list of usernames and passwords pulled from a leaked credential dump. The user agent strings are often generic browser strings spoofed to look legitimate, and the requests arrive far faster than a human could type. A single botnet can spread thousands of attempts across a wide pool of compromised residential IPs specifically to defeat simple rate limiting, which is why blocking one IP at a time rarely solves the problem.

You will also frequently see the same pattern against xmlrpc.php, using its system.multicall method to bundle many login attempts into a single request. This is an older but still common variant, since it lets an attacker test hundreds of credential pairs in one HTTP call instead of hundreds.

Why wp-login Is Such a Popular Target

WordPress powers a huge share of the web, its login endpoint is at a predictable, well-known URL, and a successful compromise gives an attacker a foothold for spam injection, malware distribution, or turning the site into part of another botnet. None of this is personal. Automated scanners do not know or care what your site is about; they are working through lists of WordPress installs and lists of leaked passwords, looking for any match at all. That is actually good news: because the attack is generic, generic layered defenses are extremely effective against it.

Layer One: Stop It at the Edge

The most efficient place to stop a login flood is before it ever reaches your server. Running Cloudflare in front of your site gives you several tools for exactly this:

  • WAF rules that can challenge or block requests to wp-login.php and xmlrpc.php based on rate, geography, or request patterns.
  • Bot mitigation that scores and filters automated traffic before it counts against your server’s resources.
  • Rate limiting to cap how many login attempts any single client can make in a given window.
  • DDoS protection, which matters because large brute-force campaigns often ride alongside volumetric attacks meant to overwhelm the origin.

One detail that is easy to overlook: none of this helps much if your origin server’s real IP address is publicly known, because an attacker can simply bypass the edge and hit the server directly. Keeping the origin IP unpublished, and making sure DNS history, old subdomains, or misconfigured plugins do not leak it, is a foundational part of DDoS and brute-force resilience.

Layer Two: Harden the Login Itself

Even with a strong edge in front of the site, it is worth assuming some traffic will get through, and making the login form itself a poor investment for an attacker:

  • Require strong, unique passwords for every account with publishing or admin access, not just the primary administrator.
  • Enable two-factor authentication wherever the login flow supports it, so a correct password alone is not enough.
  • Use application passwords for any external tool or integration that needs API access, rather than sharing full account credentials.
  • Disable XML-RPC entirely if you do not rely on it for the Jetpack app, remote publishing, or pingbacks, since it removes an entire class of amplified login attempts.
  • Limit login attempts per IP or account at the application layer as a backstop for whatever gets past the edge.

Renaming the login URL away from the default is sometimes suggested as a defense. It does reduce the volume of automated noise hitting that exact path, but it is security through obscurity rather than a real control, so treat it as a minor convenience rather than a substitute for strong passwords and two-factor authentication.

Layer Three: Audit and Respond with wp-cli

wp-cli is useful both for hardening accounts proactively and for cleaning up after a suspected attack. A few commands worth knowing:

# List all users and their roles
wp user list --fields=ID,user_login,user_email,roles

# Force a password reset for a specific user
wp user update 5 --user_pass=$(openssl rand -base64 24)

# Remove an account you do not recognize
wp user delete 12 --reassign=1

# Check for unexpected new admin accounts
wp user list --role=administrator --fields=ID,user_login,user_registered

Running a quick audit of administrator accounts periodically, especially after a spike in login traffic, is a cheap habit that catches the rare case where a brute-force attempt actually succeeded before you noticed.

What Success Looks Like

A well-defended site does not necessarily see zero login attempts; it sees the flood absorbed. The edge challenges or drops most of the automated traffic, the requests that do get through are met with rate limits and two-factor prompts, and the whole event shows up in your logs as a brief spike in blocked or challenged requests rather than a security incident. This is also an area where good managed hosting quietly does a lot of the heavy lifting: with Cloudflare and server-level protections already in place in front of the origin, a platform like ServerBorn absorbs most of this traffic before it ever becomes your problem to solve at 2am.

The Takeaway

Brute-force campaigns against wp-login are constant, automated, and impersonal, which is exactly why layered, generic defenses work so well against them. Push as much of the fight to the edge as possible, harden the login itself with strong passwords and two-factor authentication, keep your origin IP out of public view, and use wp-cli to audit accounts periodically. None of this is exotic. It is the same handful of controls, applied consistently, that turns a scary-looking log file into a non-event.