WordPress

WordPress REST API returning 403: the permalink and security-plugin fixes

A 403 from the WordPress REST API almost always traces back to one of four sources: permalinks, a security plugin's firewall rules, .htaccess, or an authentication header being stripped. See how to isolate which one.

V Vinod Kulkarni
12 August 2026 · 9 min read
WordPress REST API returning 403: the permalink and security-plugin fixes

WordPress REST API returning 403: the permalink and security-plugin fixes

TL;DR: A 403 from /wp-json/ almost never means "you are not allowed to do this" in the way a login-related 403 would. It means a request never reached WordPress's own permission checks at all, something in front of WordPress, a security plugin, a firewall rule, a broken permalink structure, stripped it first. The fix is nearly always in .htaccess, permalink settings, or a security plugin's REST API restrictions, not in WordPress's user roles.

Why REST API 403s are different from normal 403s

A normal 403 in WordPress, hitting wp-admin without the right role, means WordPress evaluated the request and decided to deny it. A REST API 403 is frequently the opposite: something never let the request reach WordPress's permission logic at all, and the 403 is coming from a layer in front of the application, a firewall, a rewrite rule, or a proxy.

This distinction matters because it means checking user roles and capabilities, the first instinct for most people, is usually the wrong starting point. The block editor, many page builders, and any plugin doing AJAX-style operations through the REST API instead of admin-ajax.php will all break identically when this happens, showing generic "the response is not a valid JSON response" or "updating failed" errors that do not obviously point at the REST API at all.

Step 1: confirm the REST API is reachable at all

Before touching permalinks or plugins, load yourdomain.com/wp-json/ directly in a browser. A working REST API returns a JSON structure describing the site's available routes. A 403 at this most basic endpoint confirms the block happens before any specific route or authentication is even considered, which narrows the cause to permalinks, a blanket firewall rule, or server-level blocking, rather than something authentication-specific.

If /wp-json/ loads fine but a specific authenticated route (like the one your page builder or app uses) returns 403, the cause is more likely Cause 4, an authentication header problem, or a security plugin rule targeting that specific route rather than the REST API as a whole.

This is the single most common cause and the fastest to check. The REST API relies on WordPress's rewrite rules to route /wp-json/ requests correctly. If permalinks are set to the default "Plain" structure (?p=123), those rewrite rules do not exist, and REST API requests can fail with a 403 or 404 depending on server configuration.

The fix: go to Settings → Permalinks and select any structure other than Plain (Post name is the standard choice), then save, which forces WordPress to regenerate its rewrite rules. This alone resolves a large share of REST API 403 reports, particularly on sites recently migrated or restored from backup, where permalink settings sometimes revert.

Cause 2: a security plugin blocking /wp-json/

Security and firewall plugins frequently include a setting, sometimes on by default, to restrict or fully disable the REST API for non-authenticated users, treating it as an attack surface to reduce rather than a feature the site depends on. This is reasonable as a general hardening step but breaks any legitimate feature relying on unauthenticated or lightly authenticated REST calls, which includes parts of the block editor, many contact form plugins, and most headless or app-connected setups.

Check the security plugin's firewall or hardening settings for anything referencing "REST API," "wp-json," or "XML-RPC and REST API restrictions." Temporarily disabling just that specific rule, not the whole plugin, isolates whether this is the cause without removing your other protections.

Rate-limiting rules are a related but distinct trigger: a plugin or server-level rule treating rapid REST API calls (common with page builders that autosave frequently) as suspicious activity and blocking the IP or session temporarily.

Cause 3: .htaccess or server rules blocking the route

On Apache-based hosting, a .htaccess rule, whether added manually, by a plugin, or left over from a previous security configuration, can block requests matching /wp-json/ in its path before WordPress ever processes them. This produces a 403 directly from the web server rather than from WordPress or a plugin, and it will not respond to disabling WordPress plugins at all, since the block happens a layer below.

Search .htaccess for any rule referencing wp-json, xmlrpc, or a broad rule blocking based on request method (some overly aggressive security hardening guides recommend blocking all PUT, DELETE, or certain POST requests at the server level, which breaks REST API operations that rely on those methods). Comment out the suspect rule, test, and restore only the parts that do not affect the API.

Server-level configuration (Nginx location blocks, for example) can have the equivalent problem outside .htaccess entirely, more common on VPS setups where the web server config was hand-edited.

Bar chart showing frequency of REST API 403 causes on WordPress sites: permalinks set to plain most common, followed by security plugin REST restrictions, htaccess or server rules, and authentication header stripping least common

Cause 4: authentication header stripped by a proxy

For REST API calls that require authentication (application passwords, JWT, or OAuth-style setups used by many headless and app-connected configurations), the Authorization header carrying the credential needs to survive the full path from client to WordPress. Some server configurations, particularly on shared hosting running PHP via CGI/FastCGI, strip the Authorization header before it reaches WordPress's PHP environment, because it is not passed through by default in that configuration.

This produces a specific pattern: unauthenticated REST requests work fine, but any authenticated request returns 401 or 403 regardless of how correct the credentials are, because WordPress never actually receives them. The fix is typically a small server configuration addition (commonly a .htaccess rule rewriting the Authorization header into an environment variable PHP can read) rather than anything in WordPress itself.

A diagnostic order

Check What it isolates
Load /wp-json/ directly, unauthenticated Whether the block is total or route-specific
Change permalinks away from Plain Rewrite rule cause
Disable security plugin's REST restriction setting Plugin-level firewall cause
Search .htaccess for wp-json or method-blocking rules Server config cause
Test an authenticated route specifically Header-stripping cause (Cause 4)
Check server error/access logs at time of request Confirms which layer generated the 403

Why this breaks page builders and the block editor specifically

Since WordPress 5.0, the block editor saves content through REST API calls rather than the older admin-ajax.php mechanism most plugins historically used. Most modern page builders followed the same pattern for their own save and preview operations. This means a REST API block that would have been invisible a decade ago now directly breaks core editing functionality, not just a peripheral feature, which is why this specific error has become far more disruptive and far more commonly reported since block editor and modern page-builder adoption became standard.

FAQs

Why does /wp-json/ work when logged in but not logged out, or vice versa?

If it only fails while logged in, check for a security plugin rule that treats authenticated dashboard sessions differently, or a nonce/session handling conflict, which is less common. If it only fails while logged out, this is consistent with a security plugin or server rule specifically restricting unauthenticated REST access, which is the more common direction for this asymmetry.

Permalinks can revert to Plain after a site migration, a backup restore that did not preserve the .htaccess file, or occasionally after a database import that did not include the rewrite_rules option correctly. If this recurs after every migration, make regenerating permalinks (visit Settings → Permalinks and re-save, even without changing anything) a standard step in your migration checklist.

Does Cloudflare or a CDN in front of my site cause REST API 403s?

It can, if a firewall rule or bot-fight mode is configured to block requests matching certain path patterns or methods, since /wp-json/ requests using POST, PUT or DELETE methods can trigger overly broad bot-protection rules designed with simple GET-based browsing in mind. Check the CDN's firewall event log for blocked requests matching wp-json in the path.

My developer says the API works with a testing tool like Postman but not from the site itself. What does that mean?

This usually points at an authentication or header issue specific to how the browser-based request is constructed, commonly a missing or incorrect nonce for logged-in AJAX-style REST calls, or a CORS-related restriction if the request is coming from a different origin than the API itself. It also rules out a blanket server-level or firewall block, since a working Postman request from a different network would likely also be blocked by anything IP-agnostic at that layer.

Is disabling the REST API entirely a safe permanent fix if I do not use page builders?

It removes the specific error but disables a feature the block editor and many core and third-party functions depend on, including the site health check, some SEO plugin features, and app connections like the WordPress mobile app. A targeted restriction to only the specific unauthenticated routes you are not using is safer than a blanket disable if the goal is reducing attack surface.

Could a caching plugin cause this instead of a security plugin?

Less commonly, but yes, if it caches API responses in a way that serves a stale 403 from a previous misconfiguration even after the underlying cause is fixed. Clear the site's full cache after resolving any of the above causes and retest, since a cached 403 response can persist and look like the fix did not work.

How do I tell whether the 403 is coming from WordPress, a plugin, or the server?

Check the response headers, not just the status code. A server-level block (Apache/Nginx) typically returns a generic server error page with no WordPress-specific headers. A WordPress or plugin-level block usually returns a JSON body with an error code and message, since WordPress's REST API is designed to respond in JSON even for errors, which a raw server-level block bypasses entirely.

Will a fresh WordPress install with no plugins ever show this 403?

Rarely, since Cause 1 (permalinks) is the only one of the four that can occur without any third-party plugin or custom server rule involved, and it is a WordPress default-state issue rather than a bug. If a clean install with default permalinks other than Plain still shows a REST API 403, the cause is almost certainly server-level configuration rather than anything in WordPress.

Conclusion

A REST API 403 is a request that got stopped before WordPress evaluated it, and the fix lives in one of four places outside WordPress's own permission system: permalink structure, a security plugin's firewall rules, server-level .htaccess or config rules, or a stripped authentication header. Checking user roles and capabilities, the natural first instinct for any 403, is rarely where the actual cause lives here.

Start with the fastest check, loading /wp-json/ directly while logged out, since that single request tells you whether the block is total (pointing at permalinks or a blanket rule) or route-specific (pointing at authentication or a targeted security rule), and saves working through the other three causes blind.

HostCloud's managed WordPress plans ship with sane default permalink and server configurations that keep the REST API reachable out of the box, so this error should only originate from a plugin-level setting you or a theme added afterward. Plans start at ₹99 a month at https://hostcloud.in, and free migration is included if you are moving an existing site.

Related posts