How to Hide WordPress Users from the REST API

maintenance

A public GET /wp-json/wp/v2/users request can reveal useful account information without requiring credentials. WordPress exposes this collection by default so themes and applications can retrieve authors, but many sites do not need anonymous visitors to query it. Hiding the route reduces easy user enumeration while leaving the rest of the WordPress REST API available.

What the Users Endpoint Exposes

The default response can include each published user's numeric ID, display name, slug, avatar URLs, and profile link. The slug is often derived from the account login, although administrators can change it and installations do not all use the same naming pattern.

Test the public response from outside an authenticated browser session:

curl -i https://example.com/wp-json/wp/v2/users

A successful JSON response does not reveal a password or grant access. It does give an attacker a list of likely account identifiers. The attacker can then target those names with password spraying, brute-force attempts, or credentials leaked from another service against wp-login.php. Knowing a valid username removes one unknown from the login attempt.

There is also an older enumeration path. Requests such as /?author=1 and /?author=2 can redirect to author archive URLs. If the archive slug reflects the login name, the redirect reveals it. REST API filtering does not close this separate front-end behavior.

Before changing anything, inventory the actual login names from a trusted shell:

wp user list --field=user_login

Compare that list with an unauthenticated REST response and author archive redirects. This establishes what is exposed and gives you a repeatable test after hardening.

1. Require Authentication on the Users Route

The least invasive application-level option is to reject anonymous requests only when the requested REST route is the users collection or an individual user. The rest_authentication_errors filter can return a WP_Error while leaving existing authentication failures intact:

<?php
add_filter(
    'rest_authentication_errors',
    function ($result) {
        if (null !== $result) {
            return $result;
        }

        $route = $GLOBALS['wp']->query_vars['rest_route'] ?? '';

        if (
            ! is_user_logged_in()
            && preg_match('#^/wp/v2/users(?:/|$)#', $route)
        ) {
            return new WP_Error(
                'rest_user_authentication_required',
                'Authentication is required to access users.',
                array('status' => 401)
            );
        }

        return $result;
    }
);

Place site-specific hardening code in a small custom plugin or must-use plugin so it is not lost when a theme changes. This approach keeps the route registered. Authenticated REST consumers can still reach it if their WordPress permissions allow the requested operation.

Test in a logged-out shell:

curl -i https://example.com/wp-json/wp/v2/users

Then test the editor and any mobile, publishing, membership, or integration workflows that read author data. Authentication and authorization are separate: requiring a login does not automatically grant every logged-in user access to every user field.

2. Remove Users Routes for Anonymous Requests

If anonymous clients should not even discover these routes, filter the registered endpoints. The following code removes both the collection and single-user patterns only for logged-out requests:

<?php
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;
    }
);

Anonymous requests now receive a no-route response, while authenticated requests retain the core route registration. This is more intervention than returning an authentication error because anonymous API clients cannot inspect the users route at all.

Some plugins register their own author or member endpoints. Removing the two core keys does not hide data exposed by unrelated namespaces, embedded author fields in other responses, feeds, sitemaps, or public author pages. Audit the resulting API rather than assuming these two unset calls eliminate every possible username signal.

3. Close the Author Archive Vector Separately

The ?author= behavior is resolved through the front-end query system, not the REST API. Sites that do not need public author archives can return a 404 for logged-out author requests:

<?php
add_action(
    'template_redirect',
    function () {
        if (is_user_logged_in() || ! is_author()) {
            return;
        }

        global $wp_query;
        $wp_query->set_404();
        status_header(404);
        nocache_headers();
    }
);

This disables public author archives, including legitimate author biography pages. A publication that depends on those archives should keep them and instead ensure public slugs do not expose login names. Changing a display name alone does not necessarily change an existing author slug, so verify the actual redirect and archive URL.

4. Add an Edge Control

A WAF or reverse proxy can block or rate-limit anonymous GET traffic whose path begins with /wp-json/wp/v2/users. This reduces traffic before PHP executes and can contain repeated probes across many sites. The rule must allow trusted authenticated workflows where required.

Edge filtering is defense in depth, not the primary data-control boundary. Headers and cookies can vary between integrations, proxy rules can be bypassed through an unprotected origin, and an application change could introduce another endpoint. Apply the scoped WordPress fix and then use the edge to reduce abusive request volume. The guide to Cloudflare firewall rules for WordPress covers path expressions, challenges, and rate limits in more detail.

Do not disable the entire REST API. Outdated snippets based on rest_enabled are incomplete for modern WordPress, and blanket denial can break the block editor, application passwords, mobile clients, Jetpack, and plugin integrations. Restrict the users route instead of turning off a core application interface.

Nova places Traefik ingress and a WAF in front of each isolated tenant on its managed WordPress hosting. That edge layer can rate-limit or block anonymous enumeration traffic before it reaches WordPress, complementing the application-level route restriction rather than replacing it. Ongoing configuration work can also be handled through WordPress maintenance.

FAQ

Does the users endpoint expose passwords?

No. The default public response does not include password hashes, but usernames or username-derived slugs still make targeted login attacks easier.

Will hiding the route stop every form of user enumeration?

No. Check author redirects, public archives, feeds, sitemaps, plugin endpoints, and content metadata separately. The REST users route is one common source, not the only possible source.

Does this break the WordPress block editor?

A correctly scoped users-route restriction should leave the broader REST API available. Test author selection and publishing workflows because a site or plugin may legitimately request user data.

Should logged-in users all see the users endpoint?

Not necessarily. WordPress capability checks and field visibility still matter, and a custom site may require stricter role-based rules. Start with anonymous denial, then evaluate authenticated access against the site's actual workflows.

See What's Included With Nova Managed Hosting

Nova runs every managed WordPress tenant in an isolated Kubernetes namespace with daily automated backups and managed core/plugin updates. If you're troubleshooting a specific issue on your own site, our team can help.