Author: Quake Media Security Team ·
To secure the WordPress REST API, disable unauthenticated access to sensitive endpoints (especially /wp-json/wp/v2/users), enforce authentication via application passwords or JWT tokens, implement rate limiting and add CORS restrictions. These steps reduce your API attack surface and block user enumeration, brute force attempts and unauthorized data scraping.
What Is the WordPress REST API and Why Is It a Security Risk?
The WordPress REST API (accessible at /wp-json/) exposes site data as JSON endpoints that themes, plugins and external applications can consume. Since WordPress 4.7, the REST API ships enabled by default with no authentication required for public endpoints. That design decision turned every WordPress install into an open data source for attackers.
The OWASP API Security Top 10 (2023 edition, still the authoritative reference in 2026) lists Broken Object Level Authorization and Unrestricted Resource Consumption as the top two API risks. WordPress core violates both out of the box. If you haven’t reviewed your site’s overall security posture, start with our WordPress security checklist before diving into REST API hardening.
Default Endpoints That Expose Your Site
A fresh WordPress install exposes these endpoints to unauthenticated requests:
/wp-json/wp/v2/users: returns usernames, user IDs, author slugs and avatar URLs. This is the single most exploited endpoint./wp-json/wp/v2/posts: full post content including drafts if permissions are misconfigured./wp-json/wp/v2/pages: complete page listing with metadata./wp-json/wp/v2/comments: comment data including email addresses (depending on configuration)./wp-json/wp/v2/settings: site configuration data (requires authentication, but probing reveals response codes)./wp-json/(root): full route index listing every registered endpoint, giving attackers a complete map of your API attack surface.
How Attackers Exploit the REST API
Attackers chain these endpoints into multi-stage attacks:
- User enumeration: a GET request to
/wp-json/wp/v2/usersreturns valid usernames. Attackers feed these into credential-stuffing tools like WPScan or Hydra. - Data scraping: bots harvest post content, comments and metadata at scale for SEO spam, content farms or competitive intelligence.
- Brute force amplification: known usernames from enumeration reduce brute force attack time by 50%+ because the attacker only needs to guess the password.
- SSRF via REST API callbacks: poorly coded plugins that register custom REST routes without proper
permission_callbackfunctions create server-side request forgery vectors. - Access control bypass: CVE-2026-3506 (WP Chatbot plugin) demonstrated how a missing capability check on a custom REST endpoint allowed unauthenticated users to read private conversations and exfiltrate PII. This pattern repeats across hundreds of plugins every year.
Understanding these attack vectors is part of a broader technical SEO checklist process. Security misconfigurations can trigger Google Safe Browsing warnings that destroy your organic visibility overnight.
7 Steps to Harden Your WordPress REST API
These are production-tested snippets from client deployments. Add them to your theme’s functions.php or (better) a custom site-specific plugin. Every snippet includes nonce verification and capability checks where applicable.
1. Block Unauthenticated User Enumeration
This is the highest-priority WordPress REST API user enumeration fix. Block the /wp-json/wp/v2/users endpoint for unauthenticated visitors while keeping it functional for logged-in administrators.
<?php
/**
* Block unauthenticated access to the users endpoint.
* Prevents user enumeration via REST API.
* Add to: functions.php or custom plugin.
*/
add_filter( 'rest_endpoints', function( $endpoints ) {
// Only restrict for non-authenticated users
if ( ! is_user_logged_in() ) {
// Remove users endpoint entirely for anonymous visitors
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;
} );Also block the legacy ?rest_route= parameter and author archive enumeration (/?author=1) with this .htaccess rule:
# Block author enumeration via query parameter
# Add to .htaccess above WordPress rewrite rules
RewriteEngine On
RewriteCond %{QUERY_STRING} ^author=\d+ [NC]
RewriteRule ^ - [F,L]2. Restrict REST API Access for Non-Authenticated Users
For sites that don’t use the REST API publicly (most brochure and business sites), restrict all REST API access to authenticated users. This is how to disable WordPress REST API for unauthenticated users while keeping essential functionality intact.
<?php
/**
* Require authentication for all REST API requests.
* Exceptions: allow specific public endpoints if needed.
* Hook: rest_authentication_errors fires before any route callback.
*/
add_filter( 'rest_authentication_errors', function( $result ) {
// Skip check if already authenticated
if ( true === $result || is_wp_error( $result ) ) {
return $result;
}
// Allow unauthenticated access to specific routes if needed
// Example: Contact Form 7 submission endpoint
$allowed_routes = array(
'/contact-form-7/', // CF7 form submissions
'/oembed/', // oEmbed for content embedding
);
$current_route = $_SERVER['REQUEST_URI'] ?? '';
foreach ( $allowed_routes as $route ) {
if ( false !== strpos( $current_route, $route ) ) {
return $result;
}
}
// Require authentication for everything else
if ( ! is_user_logged_in() ) {
return new WP_Error(
'rest_not_logged_in',
__( 'Authentication required.' ),
array( 'status' => 401 )
);
}
return $result;
} );Important: if you run WooCommerce, a headless frontend or any plugin that relies on public REST API access, use Step 1 selectively instead of this blanket restriction.
3. Implement Rate Limiting on API Endpoints
WordPress has no built-in rate limiting. This server-level approach using .htaccess and mod_evasive is more reliable than PHP-based solutions because it blocks requests before WordPress even loads.
# Rate limit REST API requests via mod_rewrite + mod_evasive
# Requires mod_evasive enabled on Apache
# Add to .htaccess
<IfModule mod_evasive20.c>
# Max requests per page per interval
DOSPageCount 10
DOSSiteCount 50
DOSPageInterval 1
DOSSiteInterval 1
DOSBlockingPeriod 60
</IfModule>
# Alternative: Nginx rate limiting (add to server block)
# limit_req_zone $binary_remote_addr zone=wpapi:10m rate=10r/s;
# location /wp-json/ {
# limit_req zone=wpapi burst=20 nodelay;
# limit_req_status 429;
# }For PHP-level rate limiting (when you can’t modify server config), use WordPress transients:
<?php
/**
* PHP-based rate limiter for REST API requests.
* Limits each IP to 60 requests per minute.
* Uses WordPress transients (stored in object cache if available).
*/
add_filter( 'rest_pre_dispatch', function( $result, $server, $request ) {
$ip = $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0';
$transient = 'rest_rate_' . md5( $ip );
$limit = 60; // requests per window
$window = 60; // seconds
$current = (int) get_transient( $transient );
if ( $current >= $limit ) {
return new WP_Error(
'rest_rate_limited',
__( 'Rate limit exceeded. Try again later.' ),
array( 'status' => 429 )
);
}
set_transient( $transient, $current + 1, $window );
return $result;
}, 10, 3 );4. Add CORS Headers to Lock Down Origins
By default, WordPress sends no CORS headers, which means browsers enforce same-origin policy. But if a plugin or custom code adds permissive CORS headers (Access-Control-Allow-Origin: *), your API becomes accessible from any domain. Explicitly restrict origins.
<?php
/**
* Restrict CORS to specific trusted origins.
* Fires on rest_pre_serve_request to set headers before output.
*/
add_action( 'rest_api_init', function() {
// Remove any existing permissive CORS headers
remove_filter( 'rest_pre_serve_request', 'rest_send_cors_headers' );
// Add restrictive CORS headers
add_filter( 'rest_pre_serve_request', function( $served ) {
$allowed_origins = array(
'https://yourdomain.com',
'https://www.yourdomain.com',
'https://staging.yourdomain.com',
);
$origin = $_SERVER['HTTP_ORIGIN'] ?? '';
if ( in_array( $origin, $allowed_origins, true ) ) {
header( 'Access-Control-Allow-Origin: ' . $origin );
header( 'Access-Control-Allow-Methods: GET, POST, OPTIONS' );
header( 'Access-Control-Allow-Headers: Authorization, Content-Type, X-WP-Nonce' );
header( 'Access-Control-Allow-Credentials: true' );
}
return $served;
} );
}, 15 );Pair this with a Content Security Policy header at the server level to prevent inline script injection on pages that consume API data.
5. Use Application Passwords or JWT for Headless Setups
If you run a headless WordPress with a decoupled frontend (Next.js, Astro, Nuxt), you need proper API authentication. WordPress 5.6+ includes application passwords natively. For stateless authentication, JWT is the better choice.
<?php
/**
* Validate JWT token on REST API requests.
* Requires: firebase/php-jwt via Composer.
* Install: composer require firebase/php-jwt
*
* WARNING: Store JWT_SECRET in wp-config.php, never in theme files.
* define( 'JWT_AUTH_SECRET', 'your-256-bit-secret-here' );
*/
use Firebase\JWT\JWT;
use Firebase\JWT\Key;
add_filter( 'rest_authentication_errors', function( $result ) {
// Skip if already authenticated (cookie-based admin session)
if ( is_user_logged_in() ) {
return $result;
}
$auth_header = $_SERVER['HTTP_AUTHORIZATION'] ?? '';
if ( empty( $auth_header ) || 0 !== strpos( $auth_header, 'Bearer ' ) ) {
return $result; // No token; let other auth methods handle it
}
$token = substr( $auth_header, 7 );
try {
$decoded = JWT::decode(
$token,
new Key( JWT_AUTH_SECRET, 'HS256' )
);
// Set the current user from the token payload
wp_set_current_user( $decoded->data->user->id );
return true;
} catch ( Exception $e ) {
return new WP_Error(
'jwt_auth_invalid_token',
$e->getMessage(),
array( 'status' => 403 )
);
}
} );For simpler setups, application passwords work well. Generate them under Users > Profile > Application Passwords, then pass them as HTTP Basic Auth with every request. Always transmit over HTTPS only.
6. Disable XML-RPC Alongside REST API Hardening
Hardening the REST API while leaving XML-RPC enabled is like locking the front door but leaving the garage open. XML-RPC supports system.multicall, which lets attackers batch hundreds of login attempts into a single HTTP request.
<?php
/**
* Disable XML-RPC entirely.
* Also removes the X-Pingback header and RSD link.
*/
// Disable XML-RPC methods
add_filter( 'xmlrpc_enabled', '__return_false' );
// Remove X-Pingback header
add_filter( 'wp_headers', function( $headers ) {
unset( $headers['X-Pingback'] );
return $headers;
} );
// Remove RSD link from head
remove_action( 'wp_head', 'rsd_link' );
// Remove wlwmanifest link (Windows Live Writer; dead since 2017)
remove_action( 'wp_head', 'wlwmanifest_link' );Supplement with an .htaccess block for defense in depth:
# Block all XML-RPC requests at the server level
# This stops the request before PHP executes
<Files xmlrpc.php>
Require all denied
</Files>7. Monitor and Log API Requests
You can’t secure what you can’t see. Log REST API requests to catch enumeration attempts, abnormal traffic patterns and unauthorized access in real time.
<?php
/**
* Log all REST API requests to a custom log file.
* Creates wp-content/rest-api.log with request details.
* Hook: rest_pre_dispatch fires before the route callback.
*/
add_filter( 'rest_pre_dispatch', function( $result, $server, $request ) {
$log_entry = sprintf(
"[%s] %s %s | IP: %s | User: %s | Agent: %s\n",
current_time( 'Y-m-d H:i:s' ),
$request->get_method(),
$request->get_route(),
$_SERVER['REMOTE_ADDR'] ?? 'unknown',
is_user_logged_in() ? wp_get_current_user()->user_login : 'anonymous',
substr( $_SERVER['HTTP_USER_AGENT'] ?? 'none', 0, 100 )
);
// Write to a log file outside the web root if possible
$log_file = WP_CONTENT_DIR . '/rest-api.log';
// Ensure log file doesn't grow unbounded (rotate at 5MB)
if ( file_exists( $log_file ) && filesize( $log_file ) > 5 * 1024 * 1024 ) {
rename( $log_file, $log_file . '.' . time() . '.bak' );
}
error_log( $log_entry, 3, $log_file );
return $result;
}, 10, 3 );Protect the log file from public access with .htaccess:
# Block direct access to REST API log file
<Files "rest-api.log">
Require all denied
</Files>For production environments, pipe these logs into a SIEM (Splunk, Elastic or Datadog) for correlation with other security events. Alerting on spikes in 401/403 responses from the REST API catches brute force campaigns within minutes.
WordPress REST API Security Checklist
Use this checklist to audit your WordPress REST API configuration. Every item should show “Done” before you consider the API hardened. For a broader security review, pair this with our full WordPress security checklist.
| Security Control | Priority | Method | Status |
|---|---|---|---|
Block unauthenticated /wp/v2/users access | Critical | PHP filter on rest_endpoints | ☐ |
| Restrict REST API to authenticated users (if applicable) | High | PHP filter on rest_authentication_errors | ☐ |
Rate limiting on /wp-json/ routes | High | Server config (mod_evasive / Nginx limit_req) | ☐ |
| CORS headers restricted to trusted origins | High | PHP filter on rest_pre_serve_request | ☐ |
| Application passwords or JWT for headless auth | Medium | WordPress native / Composer package | ☐ |
| XML-RPC disabled | High | PHP filter + .htaccess block | ☐ |
| REST API request logging enabled | Medium | PHP filter on rest_pre_dispatch | ☐ |
All custom endpoints use permission_callback | Critical | Code review / register_rest_route() audit | ☐ |
| Nonce verification on state-changing requests | Critical | wp_verify_nonce() in callbacks | ☐ |
| Content Security Policy header configured | Medium | Server-level header or PHP header() | ☐ |
| Author archive enumeration blocked | Medium | .htaccess RewriteRule | ☐ |
REST API route index (/wp-json/) restricted | Low | PHP filter on rest_endpoints | ☐ |
Small businesses running WordPress should treat API security as part of their broader digital strategy. Our guide on SEO for small business covers how security directly impacts your search rankings and online credibility.
Frequently Asked Questions
Should I disable the WordPress REST API completely?
Only if your site has zero dependencies on it. The block editor (Gutenberg), many contact form plugins (including Contact Form 7) and WooCommerce all require the REST API to function. A better approach is selective hardening: block unauthenticated access to sensitive endpoints like /wp/v2/users while allowing the routes your plugins need. Full disabling breaks more than it fixes on most production sites.
Does the REST API expose my admin username?
Yes, by default. A simple GET request to /wp-json/wp/v2/users returns usernames, display names and author slugs for every user who has published content. This is the first step in most WordPress brute force attack chains. Step 1 in this guide blocks that endpoint for unauthenticated visitors.
What is a permission_callback and why does it matter?
When you register a custom REST route with register_rest_route(), the permission_callback parameter defines who can access it. If you omit it or set it to __return_true, anyone on the internet can hit that endpoint. CVE-2026-3506 in the WP Chatbot plugin was caused by exactly this mistake: a missing permission_callback exposed private chat logs. Always use capability checks like current_user_can( 'manage_options' ) in your callbacks.
Is XML-RPC still a security risk in 2026?
Absolutely. While the REST API has replaced most XML-RPC functionality, the xmlrpc.php endpoint still exists in WordPress core and supports system.multicall for batched authentication attempts. Attackers routinely use it for brute force attacks because a single HTTP request can test hundreds of password combinations. Disable it unless you specifically need it for Jetpack or the WordPress mobile app (both of which have REST API alternatives now).
How do I test if my REST API is properly secured?
Run these three checks from any terminal. First: curl -s https://yoursite.com/wp-json/wp/v2/users should return a 401 or 403 error, not a JSON array of users. Second: curl -s https://yoursite.com/wp-json/ should either be blocked or return a limited route index. Third: use WPScan (wpscan --url yoursite.com --enumerate u) to verify user enumeration is blocked. If any of these return user data, your API is exposed.
What is the OWASP API Security Top 10 and how does it apply to WordPress?
The OWASP API Security Top 10 is a standardized list of the most critical API security risks. WordPress REST API installations are vulnerable to at least four of the ten by default: Broken Object Level Authorization (unauthenticated user data access), Unrestricted Resource Consumption (no rate limiting), Security Misconfiguration (permissive defaults) and Improper Inventory Management (full route index exposed). The hardening steps in this guide address all four.
Do security plugins like Wordfence handle REST API hardening?
Partially. Wordfence and iThemes Security offer some REST API restrictions (user enumeration blocking and rate limiting), but they apply broad rules that can break plugin functionality. They also add PHP overhead on every request. The server-level and targeted PHP approaches in this guide give you more granular control with less performance impact. Use security plugins as a complement, not a replacement, for manual hardening.
When to Call in a Professional
The steps above handle the most common REST API attack vectors, but every WordPress installation is different. If you run WooCommerce, a headless frontend, custom plugins with REST routes or a multi-site network, the hardening configuration gets more complex. A misconfigured permission_callback or an overly aggressive rate limit can break checkout flows and degrade user experience.
Quake Media’s WordPress development team audits and hardens REST API configurations for businesses across Vancouver and beyond. We test every change against your specific plugin stack and deployment environment before pushing to production. Our SEO team ensures that security hardening never conflicts with search engine crawling or indexing requirements.
Not sure where your site stands? Start with a free website audit to identify security gaps in your WordPress configuration.
Phone: 604-901-7668
Email: info@quakemedia.ca
Need help with this?
Quake Media helps businesses across Vancouver and Canada with SEO, PPC and custom web development. Get a free audit and see where your site stands.


