WordPress REST API Performance: Disable What You Don’t Need

WordPress REST API Performance: Disable What You Don’t Need - MakeWPFast

Every WordPress request to /wp-json/ boots the entire REST API infrastructure. That means 40+ endpoint controllers get instantiated, routes get registered, and hooks fire – even if the request only needs one endpoint.

Most WordPress sites don’t need 90% of these endpoints. But they’re all there, loaded and ready, on every single REST request. And on non-REST requests, WordPress still outputs REST discovery links in your HTML head and HTTP headers.

Here’s what’s actually happening, which endpoints are safe to remove, and how to audit what’s hitting your REST API.

What the REST API Loads on Every Request

Even on regular frontend page loads, WordPress adds two things related to the REST API:

  1. A <link> tag in wp_head via rest_output_link_wp_head (priority 10)
  2. A Link: HTTP header via rest_output_link_header on template_redirect (priority 11)

These are minor. The real cost comes when something actually hits /wp-json/.

When a REST request comes in, WordPress calls rest_api_loaded() from parse_request, which triggers rest_get_server() and fires the rest_api_init hook. Inside that hook, create_initial_rest_routes() runs at priority 99 and registers every single default endpoint controller.

I counted them in WordPress 6.7 core. Here’s the full list of controllers that get instantiated:

  • Posts, pages, and every custom post type with show_in_rest => true
  • Revisions and autosaves for each of those
  • Post types and post statuses
  • Taxonomies and terms
  • Users and application passwords
  • Comments
  • Search (posts, terms, post formats)
  • Block renderer, block types, blocks
  • Settings
  • Themes and plugins
  • Sidebars, widget types, widgets
  • Block directory and pattern directory
  • Block patterns and block pattern categories
  • Site health
  • URL details
  • Menu locations
  • Site editor export
  • Navigation fallback
  • Font collections
  • Abilities (categories, run, list)

That’s 40+ controller classes. Each one calls register_routes(), which means regex patterns get compiled for every route. On a site with WooCommerce, Yoast, or any plugin that adds REST endpoints, you can easily hit 200+ registered routes.

The Security Problem: User Enumeration

Here’s the part most people miss. By default, anyone can hit:

https://yoursite.com/wp-json/wp/v2/users

And get back a JSON response with usernames, user IDs, and profile URLs. No authentication needed.

This is a known attack vector – CVE-2017-5487 originally, but the behavior still exists by design. Attackers use it to harvest usernames for brute-force login attempts. Combined with XML-RPC’s system.multicall (which you should already have disabled – see our XML-RPC security guide), it gives attackers everything they need to start password spraying.

How to Audit Which Endpoints Are Being Hit

Before you start removing things, figure out what’s actually being used. There are a few ways to do this.

Method 1: Server Access Logs

If you have access to your server logs, filter for /wp-json/:

grep "wp-json" /var/log/nginx/access.log | awk '{print $7}' | sort | uniq -c | sort -rn | head -20

This shows you which REST endpoints get the most traffic. You might be surprised – on most sites, it’s the block editor endpoints and heartbeat-related calls from the admin.

Method 2: WordPress Debug Logging

Add a quick logger to see REST requests in real time:

add_filter( 'rest_pre_dispatch', function( $result, $server, $request ) {
    if ( defined( 'WP_DEBUG_LOG' ) && WP_DEBUG_LOG ) {
        error_log( sprintf(
            'REST API: %s %s (user: %d)',
            $request->get_method(),
            $request->get_route(),
            get_current_user_id()
        ) );
    }
    return $result;
}, 10, 3 );

Run this for a day, then check wp-content/debug.log. You’ll see exactly what’s calling what.

Method 3: Query Monitor

The Query Monitor plugin shows REST API calls in its HTTP API panel. Good for development, but don’t run it in production.

Which Endpoints Are Safe to Disable

This depends on your setup. Here’s my breakdown:

Safe to Remove on Most Sites

These endpoints serve the block editor, widget system, and site editor. If you’re using a classic theme (like GeneratePress) and the classic editor, you don’t need them:

  • /wp/v2/block-renderer – Block rendering
  • /wp/v2/block-types – Block type definitions
  • /wp/v2/blocks – Reusable blocks
  • /wp/v2/block-directory – Block directory search
  • /wp/v2/block-patterns – Block patterns
  • /wp/v2/block-pattern-categories – Block pattern categories
  • /wp/v2/sidebars – Widget sidebars
  • /wp/v2/widget-types – Widget type definitions
  • /wp/v2/widgets – Widgets
  • /wp/v2/navigation – Navigation fallback
  • /wp/v2/templates – Site editor templates
  • /wp/v2/template-parts – Template parts
  • /wp/v2/global-styles – Global styles
  • /wp/v2/menu-locations – Menu locations (REST)
  • /wp/v2/font-collections – Font collections
  • /wp/v2/font-families – Font families

Safe to Restrict to Authenticated Users

These leak information publicly but are needed for admin functionality:

  • /wp/v2/usersDisable for unauthenticated requests (user enumeration)
  • /wp/v2/settings – Site settings
  • /wp/v2/plugins – Plugin list
  • /wp/v2/themes – Theme information
  • /wp/v2/site-health – Site health data

Don’t Touch These

  • /wp/v2/posts and /wp/v2/pages – Needed if anything reads your content via API
  • /wp/v2/media – Image uploads from the editor
  • /wp/v2/categories and /wp/v2/tags – Taxonomy queries
  • /wp/v2/comments – Comment system (if you use it)
  • /wp/v2/search – Site search

The Code: Removing Unnecessary Endpoints

Here’s a practical mu-plugin that removes endpoints you don’t need. Drop this in wp-content/mu-plugins/:

<?php
/**
 * Plugin Name: Disable Unused REST API Endpoints
 * Description: Removes REST API endpoints that aren't needed on this site.
 */

add_filter( 'rest_endpoints', function( $endpoints ) {
    // Block editor endpoints (safe to remove with classic editor)
    $remove_patterns = array(
        '/wp/v2/block-renderer',
        '/wp/v2/block-types',
        '/wp/v2/blocks',
        '/wp/v2/block-directory',
        '/wp/v2/block-patterns',
        '/wp/v2/block-pattern-categories',
        '/wp/v2/sidebars',
        '/wp/v2/widget-types',
        '/wp/v2/widgets',
        '/wp/v2/navigation',
        '/wp/v2/templates',
        '/wp/v2/template-parts',
        '/wp/v2/global-styles',
        '/wp/v2/menu-locations',
        '/wp/v2/font-collections',
        '/wp/v2/font-families',
        '/wp/v2/font-faces',
    );

    foreach ( $endpoints as $route => $endpoint ) {
        foreach ( $remove_patterns as $pattern ) {
            if ( strpos( $route, $pattern ) === 0 ) {
                unset( $endpoints[ $route ] );
                break;
            }
        }
    }

    return $endpoints;
} );

Blocking User Enumeration

This one’s non-negotiable. Restrict the users endpoint to authenticated requests:

add_filter( 'rest_endpoints', function( $endpoints ) {
    if ( ! is_user_logged_in() ) {
        if ( isset( $endpoints['/wp/v2/users'] ) ) {
            unset( $endpoints['/wp/v2/users'] );
        }
        if ( isset( $endpoints['/wp/v2/users/(?P<id>[\d]+)'] ) ) {
            unset( $endpoints['/wp/v2/users/(?P<id>[\d]+)'] );
        }
    }
    return $endpoints;
} );

After adding this, test it:

curl -s https://yoursite.com/wp-json/wp/v2/users | head -c 200

You should get a rest_no_route error instead of a list of usernames.

Removing REST API Discovery Links

If you don’t expose any public API, remove the discovery links from your HTML:

// Remove <link> from wp_head
remove_action( 'wp_head', 'rest_output_link_wp_head', 10 );

// Remove Link: header
remove_action( 'template_redirect', 'rest_output_link_header', 11 );

These are in wp-includes/default-filters.php at lines 331-332. Removing them saves a small amount of HTML output and stops advertising your API endpoint to scanners.

The Nuclear Option: Require Authentication for Everything

If your site doesn’t serve any public REST API consumers (no headless frontend, no mobile app, no external integrations), you can lock down the entire API:

add_filter( 'rest_authentication_errors', function( $result ) {
    if ( true === $result || is_wp_error( $result ) ) {
        return $result;
    }

    if ( ! is_user_logged_in() ) {
        return new WP_Error(
            'rest_not_logged_in',
            'REST API access restricted.',
            array( 'status' => 401 )
        );
    }

    return $result;
} );

Warning: This breaks any plugin that makes unauthenticated REST calls from the frontend. Contact Form 7, some caching plugins, and various page builders use the REST API from the browser without authentication. Test thoroughly.

What About Performance Impact?

I want to be honest here – the performance gain from removing REST endpoints on non-REST requests is zero. The rest_api_init hook only fires when something actually hits /wp-json/. On regular page loads, removing endpoints changes nothing.

Where it matters:

  1. Admin pages – The block editor makes dozens of REST calls. Fewer registered routes means slightly faster route matching.
  2. REST-heavy plugins – WooCommerce, Jetpack, and similar plugins add their own endpoints. The more routes registered, the longer route matching takes.
  3. Security scanning – Bots constantly probe /wp-json/wp/v2/users. Returning a 401 early saves processing time compared to running the full users query.

The real win is security, not speed. But if you’re running a site that serves REST API responses at scale (headless WordPress, mobile app backend), trimming unused routes does reduce the regex matching overhead in WP_REST_Server::dispatch().

Monitoring REST API Usage with WP Multitool

If you’re using WP Multitool, the Slow Query Analyzer and Find Slow Callbacks modules help you spot REST API-related performance issues. The Slow Query Analyzer catches expensive database queries triggered by REST endpoints, while Find Slow Callbacks profiles hooks – including those firing on rest_api_init. This is particularly useful for finding plugins that register heavy callbacks on REST requests when they shouldn’t be.

Quick Checklist

Before you deploy any of these changes:

  1. Audit first – Log REST requests for at least 24 hours before removing anything
  2. Test the block editor – If you use Gutenberg, don’t remove block-related endpoints
  3. Check contact forms – Many form plugins use REST API for submissions
  4. Verify after deployment – Hit /wp-json/ and confirm removed endpoints are gone
  5. Monitor error logs – Watch for rest_no_route errors from legitimate functionality

Related guides that complement this one:

The REST API isn’t going anywhere – WordPress core depends on it. But there’s no reason to serve 200+ routes when your site only uses 10 of them. Trim the fat, lock down the users endpoint, and monitor what’s actually being requested. That’s the pragmatic approach.

Get WordPress Performance Tips

Join developers and agency owners who get backend optimization strategies, tool releases, and deep-dive guides.

No spam. Unsubscribe anytime. We respect your privacy.