
Before anyone can brute force a login, they need a username. Most WordPress sites give that away for free through two built-in features: author archives and the REST API’s users endpoint. Neither is a bug. Both are original WordPress features doing exactly what they were built to do. The fix is to control what they expose, not to panic about them.
How the leak actually works
WordPress assigns every post an author, and every author gets an archive page at a predictable URL pattern: /?author=1, /?author=2, and so on. Visit that URL and WordPress redirects to the author’s pretty permalink, something like /author/robbie/. That reveals the login name (or a close variant of it) with zero authentication required. An attacker can loop through low integers and build a list of valid usernames in minutes.
The REST API adds a second, often richer leak. By default, /wp-json/wp/v2/users returns a JSON list of user objects, including slug values that usually match the login name, on any site that has not restricted it. Try it yourself:
curl -s https://example.com/wp-json/wp/v2/usersIf that returns an array of names instead of an authentication error, you are exposing your author list to anyone who asks.
Why this matters even with strong passwords
A strong, unique password plus two factor authentication makes a harvested username mostly useless on its own. But usernames still lower the cost of an attack: credential stuffing tools work faster with a known username list, and a leaked username can be combined with other data (breached password reuse, phishing, social engineering) to make the whole chain easier. Closing the enumeration leak does not replace good password hygiene, it removes one input attackers rely on.
Check your exposure first
Run these two tests against your own domain before changing anything:
curl -sI https://example.com/?author=1
curl -s https://example.com/wp-json/wp/v2/usersIf the first command returns a redirect to a URL containing a real username, and the second returns a JSON array instead of an error, both leaks are live. Also audit whether your display names match your login names with wp-cli:
wp user list --fields=ID,user_login,display_nameIf user_login and display_name are identical for admin-level accounts, fix that first. Set a distinct display name so even if it leaks somewhere, it does not hand over the actual login credential.
wp user update 1 --display_name="Site Editor"Closing the author archive leak
The cleanest fix is a rewrite rule that redirects author-query requests before WordPress processes them. On Apache or LiteSpeed, add this near the top of your .htaccess, above the WordPress block:
RewriteCond %{QUERY_STRING} ^author=([0-9]*)
RewriteRule ^$ /? [L,R=302]This sends anyone probing ?author=1 back to the homepage instead of your author archive. If you actually use author archives for a multi-author blog, you can instead keep them but make sure permalinks show a name that is not the raw login, since WordPress derives the archive slug from user_nicename, which you can set separately from the login name:
wp user update 1 --user_nicename="editorial-team"Many hardening plugins offer a toggle for this as well, so a code change is not mandatory. Pick whichever method your team can maintain.
Closing the REST API leak
Do not disable the REST API wholesale. Blocks, the block editor, many plugins, and mobile clients depend on it, and a blanket shutdown tends to cause more support tickets than it prevents attacks. Instead, restrict just the users endpoint. Add this to a must-use plugin or your theme’s functions.php:
add_filter( 'rest_endpoints', function( $endpoints ) {
if ( isset( $endpoints['/wp/v2/users'] ) ) {
unset( $endpoints['/wp/v2/users'] );
}
if ( isset( $endpoints['/wp/v2/users/(?P[\d]+)'] ) ) {
unset( $endpoints['/wp/v2/users/(?P[\d]+)'] );
}
return $endpoints;
} ); This removes the public users listing while leaving the rest of the REST API, including everything logged-in editors and the block editor need, fully functional. Several well-maintained security plugins expose the same behavior through a settings toggle if you would rather not touch code directly.
After applying either fix, re-run the curl tests above. The author archive should redirect harmlessly, and the users endpoint should return a 401 or an empty response rather than a name list.
The rest of the hardening picture
Enumeration fixes are one layer. Pair them with the standard levers that actually stop an attacker who does get a username:
- Strong, unique passwords and two factor authentication on every account with publishing or admin access.
- Limiting login attempts so brute forcing a known username is slow and noisy rather than silent.
- Disabling XML-RPC if you do not use the mobile app or remote publishing, since it is another surface that accepts repeated authentication attempts.
- Setting
DISALLOW_FILE_EDITinwp-config.phpso a compromised editor account cannot rewrite theme or plugin files from the dashboard. - Least-privilege roles: not every contributor needs to be an administrator, and fewer admin accounts means fewer valuable usernames to leak in the first place.
- Tested, off-site backups, so if enumeration does lead somewhere worse, you have a clean restore point rather than a scramble.
If your site sits behind Cloudflare, you get an additional layer for free: its WAF and bot mitigation can throttle or challenge the kind of rapid, sequential requests that author-ID scraping produces, and keeping your origin IP unpublished means attackers cannot simply route around Cloudflare to hit your server directly.
Takeaway
User enumeration is a quiet leak, not a dramatic exploit, which is exactly why it goes unnoticed for years on so many sites. Test your own author archive and REST API endpoint today, apply the rewrite rule and the endpoint filter, tidy up any display names that match login names, and layer on the standard hardening list you already know. None of this is exotic. It is the difference between handing attackers a target list and making them work for every piece of information they get.