WordPress REST API Security: How to Lock It Down
Learn how to secure the WordPress REST API against user enumeration, data exposure, and abuse. Configure authentication, rate limiting, and namespace filtering.
Introduction: The REST API Is an Open Door by Default
Since WordPress 4.7, the REST API has been a core part of WordPress. It powers the block editor (Gutenberg), enables mobile apps and headless front ends, and provides a standardized interface for plugins and external services to interact with WordPress data. It is also, by default, one of the largest and most accessible attack surfaces on any WordPress installation.
Out of the box, the WordPress REST API exposes a significant amount of information to unauthenticated users. Anyone can query /wp-json/wp/v2/users and get a list of all users who have published posts, including their usernames, display names, and user IDs. Anyone can enumerate posts, pages, categories, tags, comments, and media. This information is freely accessible, no authentication required, and it gives attackers everything they need to begin targeted attacks.
This guide explains the security risks of the WordPress REST API, how to restrict access without breaking functionality, and how the VistoShield Firewall & WAF provides granular REST API access controls out of the box.
What the REST API Exposes by Default
Understanding the scope of the default REST API exposure is the first step toward securing it. Here is what is accessible without any authentication:
User Enumeration
The most significant default exposure is user enumeration via /wp-json/wp/v2/users:
# Anyone can run this against your site
curl -s https://yoursite.com/wp-json/wp/v2/users | python3 -m json.tool
# Returns:
[
{
"id": 1,
"name": "John Admin",
"slug": "johnadmin",
"link": "https://yoursite.com/author/johnadmin/",
"avatar_urls": { ... }
},
{
"id": 2,
"name": "Jane Editor",
"slug": "janeeditor",
"link": "https://yoursite.com/author/janeeditor/"
}
]
This gives attackers valid usernames for brute-force attacks. The slug field is the login username in most cases. Combined with a brute-force tool, this turns a blind guessing attack into a targeted one.
Content Discovery
All published content is accessible via the API:
# Enumerate all published posts
curl -s https://yoursite.com/wp-json/wp/v2/posts?per_page=100
# Enumerate all published pages
curl -s https://yoursite.com/wp-json/wp/v2/pages?per_page=100
# Enumerate categories and tags
curl -s https://yoursite.com/wp-json/wp/v2/categories
curl -s https://yoursite.com/wp-json/wp/v2/tags
# Enumerate media (uploaded files with URLs)
curl -s https://yoursite.com/wp-json/wp/v2/media?per_page=100
# Enumerate comments (including commenter names and emails in some cases)
curl -s https://yoursite.com/wp-json/wp/v2/comments
Site Information
The API root endpoint reveals site information and available namespaces:
# Site information and available API endpoints
curl -s https://yoursite.com/wp-json/
# Returns: site name, description, URL, namespaces, routes, and authentication methods
This tells attackers what plugins are installed (plugins register their own namespaces, e.g., /wp-json/wc/v3/ for WooCommerce) and what API functionality is available.
Security Risks of an Unrestricted REST API
Risk 1: User Enumeration for Brute-Force Attacks
As shown above, the REST API reveals valid usernames. An attacker combines these with a password list and targets wp-login.php or xmlrpc.php. Without rate limiting or Login Guard protections, this can succeed against weak passwords. For a deep dive on login protection, see our guide on protecting WordPress from brute-force attacks.
Risk 2: Content Scraping at Scale
The REST API returns structured JSON data, making it trivial to scrape your entire site’s content programmatically. Competitors, content farms, and AI training pipelines can harvest your content far more efficiently through the API than by scraping HTML pages.
Risk 3: Plugin API Endpoint Vulnerabilities
Plugins that register custom REST API endpoints sometimes fail to implement proper authentication or input validation. A vulnerability in a plugin’s custom endpoint can expose sensitive data or allow unauthorized actions. The VistoShield Security Scanner checks for known vulnerabilities in installed plugins, including REST API-specific issues.
Risk 4: Denial of Service via API Abuse
The REST API supports complex queries (filtering, sorting, searching, embedding related resources) that can be computationally expensive. An attacker can craft requests that force heavy database queries, consuming server resources and potentially causing a denial of service for legitimate visitors.
Risk 5: Authenticated Endpoint Exploitation
If an attacker obtains valid credentials (through the user enumeration described above, credential stuffing, or phishing), they can use the REST API to make changes programmatically. The API supports creating posts, modifying settings, installing plugins (with admin credentials), and more — all without touching the WordPress admin dashboard.
Method 1: Disable User Enumeration
The single most important REST API security measure is disabling user enumeration. Here are several approaches:
Using VistoShield Firewall
The VistoShield Firewall & WAF includes a dedicated toggle to block REST API user enumeration. When enabled, it intercepts requests to /wp-json/wp/v2/users and returns a 403 Forbidden response for unauthenticated requests while still allowing authenticated administrators to use the endpoint. This is the simplest and most reliable approach.
Using Code in functions.php or a Custom Plugin
// Block unauthenticated access to the users endpoint
add_filter('rest_endpoints', function ($endpoints) {
if (!is_user_logged_in()) {
// Remove users endpoint for unauthenticated requests
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 Author Archive Enumeration
The REST API is not the only way to enumerate users. WordPress author archives (/?author=1) also reveal usernames. Block this too:
// Block author enumeration via /?author=N
add_action('template_redirect', function () {
if (is_author() && !is_user_logged_in()) {
wp_redirect(home_url(), 301);
exit;
}
});
// Block author enumeration via REST API and query parameter
add_filter('rest_request_before_callbacks', function ($response, $handler, $request) {
if (preg_match('/\/wp\/v2\/users/', $request->get_route()) && !is_user_logged_in()) {
return new WP_Error('rest_forbidden', 'User enumeration is disabled.', ['status' => 403]);
}
return $response;
}, 10, 3);
Method 2: Require Authentication for the REST API
If your site does not use the REST API for public-facing features (headless front end, mobile app, external integrations), you can require authentication for all API requests.
Require Authentication for All Endpoints
// Require authentication for all REST API requests
add_filter('rest_authentication_errors', function ($result) {
// If a previous authentication check already failed, pass it through
if (is_wp_error($result)) {
return $result;
}
// Allow unauthenticated access only if already authenticated
if (!is_user_logged_in()) {
return new WP_Error(
'rest_not_logged_in',
'API access requires authentication.',
['status' => 401]
);
}
return $result;
});
Warning: This will break the block editor (Gutenberg) for logged-out users who preview content, and it may break plugins that rely on public API access (e.g., contact form submissions, caching plugins). Test thoroughly before deploying.
Selective Authentication by Namespace
A more nuanced approach is to require authentication only for specific namespaces while leaving others open:
// Require authentication for wp/v2 namespace only
add_filter('rest_authentication_errors', function ($result) {
if (is_wp_error($result)) {
return $result;
}
// Get the current request route
$route = untrailingslashit($_SERVER['REQUEST_URI']);
// Require auth for core WordPress API endpoints
if (strpos($route, '/wp-json/wp/v2') !== false && !is_user_logged_in()) {
return new WP_Error(
'rest_not_logged_in',
'Authentication required for this endpoint.',
['status' => 401]
);
}
return $result;
});
Method 3: Rate Limiting API Requests
Rate limiting prevents abuse of the REST API for denial of service, brute-force attacks, and content scraping. The VistoShield Firewall includes built-in rate limiting for REST API endpoints, configurable per IP address.
How VistoShield Rate Limiting Works
| Setting | Default Value | Description |
|---|---|---|
| Global API rate limit | 60 requests/minute per IP | Maximum API requests from a single IP per minute |
| Authentication endpoint limit | 5 requests/minute per IP | Limits login/token requests to prevent brute-force |
| Search endpoint limit | 10 requests/minute per IP | Limits expensive search queries |
| Burst allowance | 10 requests | Allows short bursts above the rate limit |
| Block duration | 15 minutes | IP blocked after exceeding limits |
For details on configuring rate limits, see the Firewall documentation.
Server-Level Rate Limiting with Nginx
If you use Nginx as a reverse proxy, you can implement rate limiting at the server level:
# In nginx.conf (http context)
limit_req_zone $binary_remote_addr zone=wpapi:10m rate=30r/m;
# In your server block
location /wp-json/ {
limit_req zone=wpapi burst=10 nodelay;
limit_req_status 429;
proxy_pass http://backend;
}
Method 4: Namespace Filtering
WordPress plugins register custom REST API namespaces (e.g., wc/v3 for WooCommerce, contact-form-7/v1 for CF7). If you know which namespaces your site needs, you can disable the rest.
Whitelist Specific Namespaces
// Only allow specific API namespaces
add_filter('rest_endpoints', function ($endpoints) {
if (is_user_logged_in()) {
return $endpoints; // Authenticated users get full access
}
// Namespaces allowed for unauthenticated access
$allowed_namespaces = [
'contact-form-7/v1', // Contact form submissions
'oembed/1.0', // oEmbed discovery
];
foreach ($endpoints as $route => $details) {
$namespace = explode('/', ltrim($route, '/'))[0] . '/' .
(explode('/', ltrim($route, '/'))[1] ?? '');
$is_allowed = false;
foreach ($allowed_namespaces as $allowed) {
if (strpos($route, '/' . $allowed) === 0) {
$is_allowed = true;
break;
}
}
if (!$is_allowed && $route !== '/') {
unset($endpoints[$route]);
}
}
return $endpoints;
});
VistoShield Firewall Namespace Controls
The VistoShield Firewall provides a GUI for managing REST API namespace access. You can enable or disable specific namespaces, set authentication requirements per namespace, and apply rate limits per namespace — all without writing code.
Method 5: Disable the REST API Link Headers
WordPress adds REST API discovery links to the HTML head and HTTP headers, advertising the API’s existence and location. Remove these to reduce information disclosure:
// Remove REST API link from HTML head
remove_action('wp_head', 'rest_output_link_wp_head', 10);
// Remove REST API link from HTTP headers
remove_action('template_redirect', 'rest_output_link_header', 11, 0);
// Remove oEmbed discovery links
remove_action('wp_head', 'wp_oembed_add_discovery_links', 10);
// Remove REST API from the WordPress RSD endpoint
remove_action('xmlrpc_rsd_apis', 'rest_output_rsd');
Note that removing these links does not disable the API itself — it only removes the advertisement. An attacker who knows to try /wp-json/ will still find the API. This is a defense-in-depth measure, not a standalone solution.
Method 6: Application Passwords and Token-Based Auth
WordPress 5.6 introduced Application Passwords, which allow users to generate separate passwords specifically for API authentication. These passwords bypass the normal login flow (and thus bypass 2FA), so they require careful management.
Securing Application Passwords
- Restrict who can create Application Passwords — Only administrators should have this ability.
- Audit active Application Passwords — The VistoShield Activity Log records creation, use, and revocation of Application Passwords.
- Set expiration policies — Revoke Application Passwords that have not been used in 90 days.
- Use one password per application — Do not share a single Application Password across multiple services.
Disabling Application Passwords
If you do not use external API integrations, disable Application Passwords entirely:
// Disable Application Passwords
add_filter('wp_is_application_passwords_available', '__return_false');
Method 7: Content Filtering in API Responses
Even when you allow API access, you may want to limit the data returned in responses. WordPress API responses often include more data than necessary.
Remove Sensitive Fields from User Responses
// Remove email and other sensitive fields from user API responses
add_filter('rest_prepare_user', function ($response, $user, $request) {
if (!current_user_can('list_users')) {
$data = $response->get_data();
unset($data['email']);
unset($data['registered_date']);
unset($data['roles']);
unset($data['capabilities']);
unset($data['extra_capabilities']);
$response->set_data($data);
}
return $response;
}, 10, 3);
Limit Post Fields Exposed
// Remove potentially sensitive fields from post API responses
add_filter('rest_prepare_post', function ($response, $post, $request) {
if (!is_user_logged_in()) {
$data = $response->get_data();
unset($data['guid']); // Internal URL
unset($data['modified']); // Last modification time
unset($data['modified_gmt']);
$response->set_data($data);
}
return $response;
}, 10, 3);
REST API Security Configuration Matrix
Different types of WordPress sites need different REST API security configurations. Use this matrix to determine the right approach for your site:
| Site Type | User Endpoint | Content Endpoints | Custom Endpoints | Rate Limiting |
|---|---|---|---|---|
| Standard blog | Block (unauthenticated) | Allow (public content) | Per-plugin basis | Yes |
| Business website | Block (unauthenticated) | Allow (public content) | Per-plugin basis | Yes |
| WooCommerce store | Block (unauthenticated) | Allow (products) | Auth required (WC API) | Yes |
| Membership site | Block (unauthenticated) | Auth required | Auth required | Yes |
| Headless WordPress | Block (unauthenticated) | Allow (with filtering) | Per-app basis | Yes |
| Internal/Intranet | Auth required | Auth required | Auth required | Optional |
Testing Your REST API Security
After implementing security measures, verify they are working correctly:
# Test 1: User enumeration should be blocked
curl -s -o /dev/null -w "%{http_code}" https://yoursite.com/wp-json/wp/v2/users
# Expected: 401 or 403 (not 200)
# Test 2: API root should still work (or not, depending on your policy)
curl -s -o /dev/null -w "%{http_code}" https://yoursite.com/wp-json/
# Expected: Depends on your configuration
# Test 3: Contact form (if using CF7) should still work
curl -s -X POST https://yoursite.com/wp-json/contact-form-7/v1/contact-forms/123/feedback \
-F "your-name=Test" -F "your-email=test@example.com" -F "your-message=Test"
# Expected: 200 (form submission should work)
# Test 4: Rate limiting should kick in
for i in $(seq 1 100); do
curl -s -o /dev/null -w "%{http_code}\n" https://yoursite.com/wp-json/wp/v2/posts
done
# Expected: 200 for first requests, then 429 (Too Many Requests)
Common Mistakes to Avoid
Mistake 1: Completely Disabling the REST API
Do not disable the REST API entirely. The block editor (Gutenberg) depends on it, and many plugins require it for core functionality. Instead, restrict access selectively as described above.
Mistake 2: Relying on Security Through Obscurity
Removing REST API link headers or changing the API prefix does not provide real security. Attackers know to try /wp-json/ and variations. Focus on authentication, rate limiting, and access controls.
Mistake 3: Forgetting XML-RPC
The REST API is not the only remote interface. xmlrpc.php provides similar functionality and is frequently exploited. If you have secured the REST API but left XML-RPC wide open, you have not solved the problem. The VistoShield Firewall can disable or restrict XML-RPC as well. See our WAF guide for details.
Mistake 4: Not Monitoring API Access
Securing the API without monitoring it means you will not know when attacks occur or when your security measures are being tested. The VistoShield Activity Log tracks API authentication attempts and security events, giving you visibility into API activity.
Getting Started with VistoShield
The WordPress REST API is a powerful feature, but it requires careful security configuration. The VistoShield Firewall & WAF provides comprehensive REST API security controls without requiring you to write code or edit configuration files. Combined with the rest of the VistoShield security suite, it provides layered protection for your WordPress site:
- Firewall & WAF — REST API access controls, user enumeration blocking, rate limiting, XML-RPC management, and application-layer request filtering.
- Login Guard — Two-factor authentication, progressive lockouts, and honeypot fields that protect the authentication layer the REST API depends on.
- Security Scanner — Vulnerability scanning that checks for known REST API security issues in your installed plugins.
- Bot Detector — Identifies automated tools that abuse the REST API for scraping, enumeration, and reconnaissance.
- Activity Log — Audit trail for all API authentication events, blocked requests, and security incidents.
All five plugins are available in the free tier under the GPLv2 open-source license. For advanced features, the Single Pro plan starts at €19/site/year, the Pro Bundle is €49/site/year, and the Agency Bundle covers 25 sites for €149/year. Visit the VistoShield homepage to get started, or explore the Firewall documentation for REST API configuration details.