WordPress hands your usernames to attackers. Here's how to shut the door.
A field report after a targeted campaign: why WordPress exposes your logins by default, and an open-source (MIT) plugin to stop it.
- Brute force needs a username AND a password — WordPress gives the username away for free through four default channels.
- ?author enumeration, the REST users endpoint, the lost-password form and login error messages all confirm which accounts exist.
- A lightweight open-source plugin closes all four, with no settings, no database tables, and no measurable performance cost.
In April 2026, a production WordPress site I maintain was hit by a targeted attack campaign — thousands of coordinated attempts over several days, not a harmless automated sweep.
Going through the logs, the brute force itself wasn't the interesting part. It was this: the attacker never had to guess the login usernames. WordPress handed them over.
A brute-force attack needs two ingredients: a username and a password. We obsess over the second one (strong passwords, 2FA, fail2ban) and ignore the first — even though WordPress broadcasts it by default, through several channels.
Why WordPress leaks your usernames by default
You can test every one of these on your own site right now. Each is a free hint handed to an attacker before any password is ever tried:
- Author enumeration: open /?author=1. If it redirects to /author/some-username/, a valid login just leaked. Iterate ?author=2, ?author=3 and you harvest every account.
- REST API user listing: /wp-json/wp/v2/users returns the list of accounts — username and slug included — to any anonymous visitor.
- Lost-password form: it confirms whether an account exists (a different message per case) and sends a real email on every submission, making it both an information leak and a harassment vector.
- Login error messages: "unknown username" versus "incorrect password" is enough to confirm which logins are real.
The common thread is simple: you cannot brute-force a username you never found. Close these four doors and you remove the reconnaissance step that makes the whole attack chain possible.
What the plugin does
I wrote a small plugin that closes exactly these four vectors. No settings page, no options screen, no database tables — only 15-minute transients for the rate limit:
- /?author=N returns a hard 404 instead of redirecting to the author archive.
- /wp-json/wp/v2/users returns 401 for anonymous visitors, while staying available to logged-in users.
- The lost-password form is capped at 5 attempts per 15 minutes per IP, with an identical response whether or not the account exists.
- Login errors collapse to a single generic message, so they no longer confirm valid usernames.
It runs on single-site and multisite, WordPress 5.x and up, PHP 7.4 and up. You activate it and it protects — there is nothing to configure.
The results
On a single site, after deployment, over 23 days:
- 36,127 enumeration attempts blocked.
- 4,842 distinct IP addresses involved.
- 0 usernames exposed through the four vectors above.
What this plugin does NOT do
Selling false peace of mind is worse than no plugin at all, so here is the honest scope:
- It does not rate-limit wp-login.php itself. High-volume brute force against the login page belongs at the server level (fail2ban, WAF). This closes the reconnaissance step that comes before it.
- Behind a proxy or CDN (Cloudflare and similar), REMOTE_ADDR is the proxy IP, so the lost-password rate limit becomes global instead of per-visitor until you restore the real client IP server-side.
- Blocking the REST users endpoint will break headless setups, or plugins that rely on anonymous access to that endpoint.
Installing it in two minutes
The plugin is published under the MIT license. Two ways to install it:
- From the WordPress admin: download the ZIP, upload it under Plugins > Add New, and activate. On activation it installs itself as a must-use plugin, so it cannot be switched off from the admin — even by a compromised account.
- Manually: copy the single mu-plugin file into wp-content/mu-plugins/ — no activation needed.
Read the code, audit it, fork it. It is short enough to review in a few minutes — which is exactly what you should expect from anything that touches your login flow.
Close the door before someone tries the handle
Username enumeration isn't a sophisticated attack — it's low-hanging fruit that WordPress leaves in plain sight. The version, the author archives, the REST API, the password form: all of them quietly confirm who can log in.
Closing them takes one lightweight plugin and zero configuration. If you manage WordPress sites, it's a five-minute change that removes the very first step every brute-force campaign relies on.