WordPress publishes a list of users who have authored posts, by default. Open /wp-json/wp/v2/users without logging in and you get names and a slug for each. On a default install, that slug is the username.
What changes once the username is known
Be precise about this: user enumeration is not a vulnerability in itself. It is a discount on the attacker’s costs. Logging in means guessing two things, a username and a password. Publish the username and it becomes a one-variable problem. Add the absence of login rate limiting and automated guessing becomes viable.
There is more than one leak.
Closing it, in two layers
The first layer is to separate the username from the public name. This is the fundamental fix: however a name leaks, if it is not the login it does not reduce the attacker’s problem.
# make the slug (user_nicename) differ from the login
wp user update 1 --user_nicename=team --display_name="WPER Team"
The second layer is to close the routes. Remove the users collection for logged-out requests and cut off author-archive probing. Leave the logged-in case alone so the block editor’s author picker keeps working.
// Drop the user routes for logged-out requests only.
add_filter( 'rest_endpoints', function ( $endpoints ) {
if ( is_user_logged_in() ) {
return $endpoints;
}
unset( $endpoints['/wp/v2/users'] );
unset( $endpoints['/wp/v2/users/(?P<id>[d]+)'] );
return $endpoints;
} );
// If the site does not use author archives, stop the probing entirely.
add_action( 'template_redirect', function () {
if ( is_author() || isset( $_GET['author'] ) ) {
wp_safe_redirect( home_url( '/' ), 301 );
exit;
}
} );
That second snippet is only correct on a site that does not use author archives. Where several writers contribute and author pages are a content asset, this code deletes an asset. Before pasting in a hardening snippet, check whether the site actually uses the feature it removes.
And one layer at the server
Having closed it in the application, add the same rule at the server. Blocking unauthenticated /wp-json/wp/v2/users and ?author= in nginx means one layer survives a plugin overriding your filter or a theme change. Two layers of the same defence is not redundancy; it is what keeps a single missing layer from becoming an opening.
Verify it, too. Hit the routes from a logged-out browser or with curl and see what is still answering. Our free diagnostic tools include this in their checklist, so a route reopening after a migration or a plugin change gets caught rather than assumed closed.
Login protection and hardening more broadly are collected in the Security archive.
Next part
That closes the security run. Next is performance: which layer should cache API responses, and how to design the genuinely hard part — invalidation.