Prevent WordPress username enumeration attacks

Username enumeration is the first step in a targeted brute-force attack: an attacker visits /?author=1 on your WordPress site, which redirects to /author/admin/ and reveals the exact login username. From there, they only need to guess the password. By default, WordPress openly exposes author slugs through author archive URLs, the REST API, and oEmbed endpoints — all intended for legitimate use but exploitable by attackers. Blocking username disclosure is a multi-layer problem because the information leaks from several places simultaneously. The .htaccess approach blocks the ?author=N query string redirect at the server level before PHP runs. A PHP hook on template_redirect catches any remaining redirect attempts and sends a 404 instead of revealing the username. Blocking the /wp-json/wp/v2/users REST endpoint prevents mass enumeration of all user accounts via the API. Changing author slugs to something that does not reveal the login name adds another layer. Combine these measures with the login rate-limiting, custom login URL, and security headers for a comprehensive defence. The snippets below implement all three layers so you can choose the depth of protection that suits your site.

Problem: Visiting /?author=1 on your WordPress site redirects to the author archive and reveals your admin login username to anyone who knows to look.

Solution: Add the server rule to .htaccess and the PHP hooks to your functions.php:

# Block ?author=N enumeration in .htaccess (add above WordPress rules)
<IfModule mod_rewrite.c>
    RewriteCond %{QUERY_STRING} ^author=\d+
    RewriteRule ^ - [F,L]
</IfModule>

/**
 * Block author archive redirect via PHP (backup layer).
 */
add_action( 'template_redirect', 'helloadmin_block_author_enum' );
function helloadmin_block_author_enum() {
    if ( is_author() && ! is_user_logged_in() ) {
        wp_redirect( home_url( '/' ), 301 );
        exit;
    }
}

/**
 * Disable the /wp-json/wp/v2/users REST endpoint for non-admins.
 */
add_filter( 'rest_endpoints', 'helloadmin_disable_users_endpoint' );
function helloadmin_disable_users_endpoint( $endpoints ) {
    if ( isset( $endpoints['/wp/v2/users'] ) && ! current_user_can( 'list_users' ) ) {
        unset( $endpoints['/wp/v2/users'] );
    }
    if ( isset( $endpoints['/wp/v2/users/(?P<id>[\d]+)'] ) && ! current_user_can( 'list_users' ) ) {
        unset( $endpoints['/wp/v2/users/(?P<id>[\d]+)'] );
    }
    return $endpoints;
}

/**
 * Remove author info from oEmbed responses.
 */
add_filter( 'oembed_response_data', 'helloadmin_remove_author_from_oembed' );
function helloadmin_remove_author_from_oembed( $data ) {
    unset( $data['author_name'], $data['author_url'] );
    return $data;
}

NOTE: Blocking author archives with a redirect (rather than a 404) is better for SEO — a 301 redirect tells search engines the content has moved, while a 404 signals a broken link. If your theme’s author archive pages provide genuine SEO value (author bios, author post lists), consider instead creating a custom author slug that differs from the login username: edit the user in Users → Edit User and change the Nickname field, then set “Display name publicly as” to the nickname.