Hushfort
Menu

How do I stop user enumeration in WordPress?

By Eureka Studio · Updated

Short answer

WordPress shows usernames in four public places: the REST endpoint /wp-json/wp/v2/users, ?author=1 redirects, author archive URLs and the core user sitemap. To hide them, block the REST list for logged-out visitors, stop ?author= scans, remove the user sitemap and make login errors identical. A plugin can do all four; test each afterwards.

Steps

  1. 1Step 1

    See what WordPress shows today

    Visit yoursite.com/wp-json/wp/v2/users while logged out. On a default install you get a JSON list with each author's login name in the slug field.

    The public REST user list at yoursite.com/wp-json/wp/v2/users showing slug demo-admin
  2. 2Step 2

    Try the author scan

    Now visit yoursite.com/?author=1. WordPress redirects to /author/your-username/, putting the name in the address bar. Bots count up 1, 2, 3 to collect every user.

    yoursite.com/?author=1 redirected to yoursite.com/author/demo-admin/, an author page titled Author: demo-admin
  3. 3Step 3

    Close the four places

    Open Hushfort → Surface Hardening and switch it on. Keep REST user list, Author scan ("Block ?author=1 scans"), Sitemap author list and oEmbed author info turned on, then save.

    Surface Hardening settings: REST user list blocked for logged-out visitors, author scans blocked, sitemap author list and oEmbed author info removed
  4. 4Step 4

    Test again from a private window

    Repeat the same requests while logged out. The user list now answers "rest_no_route" with a 404, and ?author=1 no longer redirects to a username.

    The same REST user list address now answering rest_no_route with status 404

Why it matters

To log in, an attacker needs two things: a username and a password. WordPress, out of the box, hands over the first half. It isn't a bug; each place exists for a reason (feeds for apps, pretty author URLs, search engines). But together they let any bot build a list of every login name on your site in seconds.

Hiding usernames doesn't make a weak password safe. What it does is remove the easy half of the puzzle, so every guess has to cover both a name and a password. Paired with login limits and two-factor login, that turns guessing from slow into hopeless.

The four places WordPress publishes usernames:

  1. REST API — /wp-json/wp/v2/users lists authors with their slug, which usually equals their login name.
  2. Author scans — /?author=1 redirects to /author/login-name/.
  3. Author archives — the archive URL itself contains the author slug.
  4. Core sitemap — /wp-sitemap-users-1.xml lists author archive URLs. oEmbed responses can also include author information.

Common mistakes

  • Fixing one place and calling it done. Closing ?author= while the REST list stays open changes nothing for a bot. Check all four.
  • Display name equals login name. Even with every leak closed, a theme that prints "Posted by admin" gives the name away. Set a different display name under Users → Profile.
  • Breaking real author pages. On a blog with several writers, author archives are useful. Block number scans instead of the archives themselves.
  • Detailed login errors. "The password you entered for the username jane is incorrect" confirms the name exists. Use one message for both cases.

Without a plugin

You can cover most of this with a must-use plugin (wp-content/mu-plugins/hide-users.php):

<?php
// 1. REST user list: logged-out visitors get nothing.
add_filter( 'rest_endpoints', function ( $endpoints ) {
    if ( ! is_user_logged_in() ) {
        unset( $endpoints['/wp/v2/users'], $endpoints['/wp/v2/users/(?P<id>[\d]+)'] );
    }
    return $endpoints;
} );

// 2. ?author=N scans: stop the redirect that reveals the name.
add_action( 'template_redirect', function () {
    if ( isset( $_GET['author'] ) && ! is_admin() ) {
        wp_safe_redirect( home_url( '/' ), 301 );
        exit;
    }
} );

// 3. Core sitemap: drop the users provider.
add_filter( 'wp_sitemaps_add_provider', function ( $provider, $name ) {
    return 'users' === $name ? false : $provider;
}, 10, 2 );

// 4. Same message for wrong username and wrong password.
add_filter( 'login_errors', function () {
    return 'The username or password is incorrect.';
} );

Caveats: test it on a staging site first, because themes and plugins sometimes read the REST user list. This doesn't change the author slug in archive URLs; for that, change each author's nicename or use a display-name plugin. And it's one more file to keep track of when you move or rebuild the site.

If something goes wrong

  • An integration lost its author list. Some external tools read /wp/v2/users without logging in. Give them an application password so they authenticate, or allow the list again.
  • Author pages show a 404. You chose to disable author archives completely. Switch to the option that blocks number scans only.
  • SEO plugin still lists authors. Many SEO plugins generate their own author sitemap. Turn author sitemaps off in that plugin too.

With Hushfort

The Username Shield, part of Hushfort's Surface Hardening layer, closes all four places in one screen:

  • blocks the REST /wp/v2/users list for visitors who aren't logged in,
  • blocks ?author=N scans while keeping /author/name/ archives (or disables archives entirely, if you prefer),
  • removes the core user sitemap and author details from oEmbed responses,
  • makes login error messages the same for a wrong username and a wrong password.

Then Surface Scan tests every place with real requests and names any username that still leaks.

Frequently asked questions

What is user enumeration in WordPress?

It's collecting a site's login names from public URLs, such as ?author=1 or the REST user list. With the names known, attackers only have to guess passwords.

Is it a security risk if my username is visible?

On its own, no, but it removes half of the guesswork. Hiding names is a small hardening step; strong passwords, login limits and two-factor do the heavy lifting.

Will blocking the REST user list break the editor?

No. Logged-in users are exempt, so the block editor and admin screens work as usual. It only matters if an outside integration reads your user list without logging in.

Can I keep author archive pages?

Yes. You can block ?author= number scans while /author/name/ archives keep working. To hide names completely, give authors a display name and author slug that differ from their login.

Why do the login error messages matter?

By default WordPress says whether the username or the password was wrong. That confirms which usernames exist. Identical messages for both remove that clue.