Guides/Stop user enumeration

WordPress security guide

How to stop WordPress user enumeration

A password-guessing run starts with a list of login names, and WordPress gives them out to anyone who asks the right URL. There are three such URLs.

Today, on most WordPress sites GET/wp-json/wp/v2/users200 OK

Every author who has published, with the login name in "slug".


After this guide GET/wp-json/wp/v2/users404 Not Found

The list needs a login; ?author=N and the users sitemap are closed too.

Why it matters

Half the loginWith the name handed out, a guessing run only has to find the password.
Real accountsThe names are real, so lockouts and alerts fire for your real users.
admin firstA user called admin is the first one every guessing list tries.

WordPress shows login names in three places: /?author=1 redirects to /author/<name>/, and counting up from 1 lists every author; /wp-json/wp/v2/users (also at /?rest_route=/wp/v2/users) returns every user who has published, with their slug; and /wp-sitemap-users-1.xml, built in since WordPress 5.5.

Check your site

Run the free check, or ask the URL yourself:

curl -sI "https://example.com/?author=1" | grep -i location
curl -s  "https://example.com/wp-json/wp/v2/users" | head -c 300

A Location header ending in /author/something/, or JSON with "slug" in it, means names are readable.

Fix it

WordPress

In a small plugin of your own, or the theme's functions.php:

// ?author=N no longer reveals a login to visitors who are not logged in.
add_action( 'template_redirect', function () {
    if ( isset( $_GET['author'] ) && ! is_user_logged_in() ) {
        wp_safe_redirect( home_url( '/' ), 301 );
        exit;
    }
} );

// The REST users list needs a login.
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;
} );

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

Note: the block editor sends its own login with every REST call, so it keeps working. Author archives at /author/<name>/ still exist; to keep the login out of those addresses too, change each author's user_nicename, or publish from a separate account.

nginx

Inside the server block, refuse the query-string form before WordPress runs:

if ($args ~* "(^|&)author=\d") {
    return 404;
}

Catch: this does not cover the REST API. Keep the WordPress filter as well.

Apache

In .htaccess, above the WordPress rules:

RewriteEngine On
RewriteCond %{QUERY_STRING} (^|&)author=\d [NC]
RewriteRule ^ - [R=404,L]

Catch: this does not cover the REST API. Keep the WordPress filter as well.

Questions

Are login names secret?

Not really, and a strong password matters more. But there is no reason to hand them out, and hiding them makes every guessing run start from nothing.

Will this break my author pages?

No. Author archives keep working through their normal links; only the ?author=N lookup and the API list are closed to anonymous visitors.

Does it affect SEO?

No. Search engines index author archives through the links on your posts, not through ?author=N or the REST API.

More guides

Last reviewed 2026-10-08