
How to prevent user enumeration in WordPress?
- Sajjad
- WordPress, Security
- 09 Sep, 2026
To prevent user enumeration in WordPress, you need to close the handful of places where usernames leak: block ?author= query scans, hide the /wp-json/wp/v2/users REST endpoint from logged-out visitors, show a generic message on failed logins, remove the users sitemap, and make sure author slugs and display names don't match login usernames. You can do all of this with a short custom plugin, a couple of server rules, or a security plugin that bundles these options.
User enumeration is the process of discovering valid usernames on a site, and it's usually the first step in a brute-force attack. In this guide, you'll learn exactly how enumeration works on WordPress, which leaks matter most, and how to shut each one down without breaking your author pages, the block editor, or your SEO.
What Is User Enumeration?
A brute-force attack needs two things: a username and a password. If an attacker has to guess both, their job is enormously harder. If they can learn your real usernames first, they only have to guess the password.
User enumeration is any technique that confirms which usernames exist on your site. On WordPress, it's especially easy by default, because several features display or reveal author information for legitimate reasons, like author archive pages and the REST API.
Enumeration on its own doesn't break into your site. But combined with weak or reused passwords, it significantly raises the chance of a successful login attack. Closing these leaks is a small effort that makes automated attacks less effective.
How Attackers Enumerate WordPress Users
Here are the most common enumeration methods on a WordPress site, so you know what you're defending against:
- Author ID scans: Visiting
https://example.com/?author=1on most WordPress sites redirects to/author/username/. Bots loop through?author=1,?author=2, and so on to collect usernames. - The REST API users endpoint:
/wp-json/wp/v2/usersreturns a JSON list of users who have published content, including aslugfield that usually matches the login username. - Login error messages: By default, WordPress says a username "is not registered on this site" for unknown names, but gives a different error when the username exists and the password is wrong. That difference confirms valid usernames.
- Password reset form: The lost password form similarly reveals whether a username or email address exists.
- Author sitemaps: WordPress core sitemaps include
/wp-sitemap-users-1.xml, which lists author archive URLs containing slugs. - oEmbed data: The oEmbed endpoint returns the author's name and author URL for a post.
- Theme output: Many themes print author links, and some add CSS classes like
author-usernameto the page body or comments.
The good news is that each one has a straightforward fix.
Step 1: Make Usernames Different From Public Names
Before adding any code, fix the root of the problem. WordPress uses the login username to create the author slug, called the user_nicename, which appears in author URLs. If your username is sajjad, your author archive is /author/sajjad/.
There are two separate fields to think about:
- Display name: What visitors see as the author name on posts. Change it under Users > Profile using the Display name publicly as dropdown. Choose your first name, full name, or a nickname instead of your username.
- Author slug (nicename): The part of the author URL. WordPress doesn't provide a setting for this in the dashboard, but the Edit Author Slug plugin adds one, or you can change it with WP-CLI.
Here's how to change a user's author slug with WP-CLI:
# Change the author slug for user ID 1 without changing the login username
wp user update 1 --user_nicename="sajjad-hossain"
# Check the result
wp user get 1 --fields=user_login,user_nicename,display_name
Once the slug and display name differ from the login username, even a successful enumeration only reveals names that don't work at the login form. For a new site, the simplest option is to create admin accounts with a non-obvious username from the start.
Step 2: Block Author ID Scans
The ?author=N redirect is the most common enumeration technique. You can stop it in WordPress or at the server level.
With PHP
Add this to a custom plugin or your child theme's functions.php. It runs before WordPress performs its canonical redirect to the author slug:
<?php
/**
* Block ?author=N enumeration scans on the front end.
*/
function sajjad_block_author_scans() {
if ( is_admin() || wp_doing_ajax() || wp_doing_cron() ) {
return;
}
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only check.
if ( isset( $_GET['author'] ) ) {
wp_safe_redirect( home_url( '/' ), 301 );
exit;
}
}
add_action( 'template_redirect', 'sajjad_block_author_scans', 1 );
It hooks into template_redirect at priority 1, so it runs before redirect_canonical(), which is the function that normally converts the author ID into a pretty URL. The is_admin() check matters: in the dashboard, WordPress uses edit.php?author=N to filter posts by author, and you don't want to break that.
With Apache or LiteSpeed
Blocking the scan at the server level is faster because PHP never runs. Add this to your root .htaccess file, above the # BEGIN WordPress block:
# Block ?author=N scans on the front end
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{REQUEST_URI} !^/wp-admin [NC]
RewriteCond %{QUERY_STRING} (^|&)author=\d+ [NC]
RewriteRule ^ - [F,L]
</IfModule>
With Nginx
Add this inside your site's server block, above your main location / block:
# Block ?author=N scans on the front end
location = / {
if ($arg_author ~ "^[0-9]+$") {
return 403;
}
try_files $uri $uri/ /index.php?$args;
}
Using if with a return inside a location is one of the safe uses of if in Nginx. Test with sudo nginx -t before reloading. Scans usually target the homepage, so this covers the typical request; the PHP method above covers any path.
Step 3: Hide the REST API Users Endpoint
The users endpoint is useful for the block editor, which uses it to populate the author selector, so don't remove it for logged-in users. Just hide it from logged-out visitors:
<?php
/**
* Hide REST API user routes from logged-out visitors.
*/
function sajjad_hide_rest_users( $endpoints ) {
if ( is_user_logged_in() ) {
return $endpoints;
}
unset( $endpoints['/wp/v2/users'] );
unset( $endpoints['/wp/v2/users/(?P<id>[\d]+)'] );
return $endpoints;
}
add_filter( 'rest_endpoints', 'sajjad_hide_rest_users' );
After adding this, /wp-json/wp/v2/users returns a 404 "No route was found" error for anonymous visitors. If you run a headless site that shows author information through the API, you'll need to authenticate those requests or expose author names another way.
Step 4: Use Generic Login Error Messages
WordPress's default login errors tell visitors whether the username exists. You can replace every login error with a single generic message using the login_errors filter:
<?php
/**
* Show a generic error on failed logins.
*/
function sajjad_generic_login_error( $error ) {
// Keep the message for empty fields so users know what went wrong.
if ( isset( $GLOBALS['errors'] ) && is_wp_error( $GLOBALS['errors'] ) ) {
$codes = $GLOBALS['errors']->get_error_codes();
if ( array_intersect( $codes, array( 'empty_username', 'empty_password' ) ) ) {
return $error;
}
}
return esc_html__( 'The login details you entered are incorrect. Please try again.', 'sajjad' );
}
add_filter( 'login_errors', 'sajjad_generic_login_error' );
This keeps helpful messages when a field is left empty, but gives the same response whether the username is wrong or the password is wrong.
Lost Password Form
The lost password form can also reveal whether an account exists. WordPress core has been moving toward generic responses here, but behaviour can vary by version and by plugins that customise the form. Test it by requesting a reset for a username that doesn't exist and one that does. If the messages differ, a security plugin can standardise them, or you can use the lostpassword_errors filter to adjust the error for unknown users.
Step 5: Remove the Users Sitemap
WordPress core sitemaps include a users sitemap that lists every author archive URL. If you don't need author archives indexed, remove it:
<?php
/**
* Remove the users provider from core WordPress sitemaps.
*/
function sajjad_remove_users_sitemap( $provider, $name ) {
if ( 'users' === $name ) {
return false;
}
return $provider;
}
add_filter( 'wp_sitemaps_add_provider', 'sajjad_remove_users_sitemap', 10, 2 );
If you use an SEO plugin like Yoast SEO, Rank Math, or All in One SEO, they replace the core sitemaps with their own. Each has a setting to exclude author sitemaps or disable author archives, usually in their sitemap or archive settings.
Step 6: Remove Author Data From oEmbed Responses
When someone embeds one of your posts on another site, WordPress returns oEmbed data that includes author_name and author_url. You can remove those fields:
<?php
/**
* Remove author details from oEmbed responses.
*/
function sajjad_oembed_remove_author( $data ) {
unset( $data['author_name'], $data['author_url'] );
return $data;
}
add_filter( 'oembed_response_data', 'sajjad_oembed_remove_author' );
This is a lower-priority leak, especially once your display names and author slugs no longer match login usernames, but it's a quick tidy-up.
Step 7: Consider Disabling Author Archives
On a single-author blog or a business site, author archive pages often duplicate your main blog page and add little value. If that's true for your site, you can redirect them to the homepage, which removes an entire category of URL that reveals author slugs:
<?php
/**
* Redirect author archives to the homepage.
*/
function sajjad_disable_author_archives() {
if ( is_author() ) {
wp_safe_redirect( home_url( '/' ), 301 );
exit;
}
}
add_action( 'template_redirect', 'sajjad_disable_author_archives' );
Only do this if you don't need author pages. Multi-author publications usually benefit from author archives for readers and search engines, so for them, Step 1 (separating slugs from usernames) is the better fix. Most SEO plugins can also disable author archives from their settings.
Using a Security Plugin Instead
If you'd rather not add code, several well-known security plugins include user enumeration protection:
- Wordfence: Has options to prevent discovery of usernames through
?author=Nscans, the oEmbed API, the REST API, and sitemaps, and to avoid revealing valid users in login errors. - Solid Security: Includes settings to restrict REST API access and harden user-related information.
- All-In-One Security (AIOS): Provides options to disable user enumeration and customise login error messages.
Check each plugin's settings after installing, since these options aren't always enabled by default. If you use a plugin for this, don't also add the same protections in code, to avoid confusing overlaps.
Putting It All Together
If you want a single place for the code in this article, create a small plugin file at wp-content/mu-plugins/sajjad-user-enumeration.php. Must-use plugins load automatically and can't be deactivated from the dashboard by accident. Start the file with a plugin header and an ABSPATH check:
<?php
/**
* Plugin Name: User Enumeration Protection
* Description: Reduces the ways usernames can be discovered on this site.
* Version: 1.0.0
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
// Paste the functions and add_action/add_filter calls from Steps 2 to 6 below.
Take a backup before adding it, then test each item in a private browser window.
How to Test for User Enumeration
After making your changes, clear any page cache and test each leak while logged out:
# Author scan: should return 403 or redirect to the homepage, not an author URL
curl -sI "https://example.com/?author=1" | grep -iE "^(HTTP|location)"
# REST API users: should return 404 or 401
curl -s -o /dev/null -w "%{http_code}\n" https://example.com/wp-json/wp/v2/users
# Users sitemap: should return 404
curl -s -o /dev/null -w "%{http_code}\n" https://example.com/wp-sitemap-users-1.xml
Then try logging in with a made-up username and with a real username plus a wrong password. Both should show the same error message.
FAQ: Preventing User Enumeration in WordPress
It's the process of discovering valid usernames on a WordPress site, using features like author ID redirects, the REST API users endpoint, and login error messages. Attackers use the usernames for brute-force login attempts.
WordPress treats public author information as a feature rather than a vulnerability. It's still worth reducing, because knowing valid usernames makes brute-force and credential-stuffing attacks easier.
No. Blocking ?author=N only stops the numeric ID lookup. Normal author archive URLs like /author/name/ keep working unless you choose to disable them.
Yes. Remove the users routes only for logged-out visitors, as shown in this guide. Logged-in editors keep access, so the author selector continues to work.
WordPress doesn't offer a dashboard setting for it. Use a plugin like Edit Author Slug, or run wp user update with the --user_nicename option in WP-CLI.
No. It makes them less effective, but you still need strong passwords, login rate limiting, and two-factor authentication for accounts with elevated access.
Conclusion
User enumeration works because WordPress shares author information in several helpful places, and each one happens to reveal something close to a login username. Closing the leaks is straightforward: separate display names and author slugs from usernames, block ?author=N scans, hide the REST users endpoint from strangers, use generic login errors, and remove author data from sitemaps and oEmbed responses where it isn't needed.
None of these changes will stop a determined attacker by themselves, but together they take away the easy first step of most automated login attacks. Combine them with strong passwords, rate limiting, and two-factor authentication, and guessing your way into your dashboard becomes a far less attractive option.


