WordPress “Invalid JSON response”: causes and fixes that actually work

Was this helpful?

The WordPress editor says “Invalid JSON response” and suddenly your “simple content update” becomes a production incident.
Posts won’t publish. Settings won’t save. The Block Editor sulks. And your browser console is quietly screaming about REST calls failing.

This error is not a mystery. It’s a symptom: WordPress expected JSON from the REST API, but got something else—an HTML login page,
a 403 from a WAF, a redirect loop, a TLS failure, a cached 404, or a plugin that “helpfully” rewrites headers.
We’ll treat it like an SRE problem: observe, narrow, prove, fix, prevent.

What “Invalid JSON response” actually means

WordPress (especially the Block Editor/Gutenberg) talks to your own site using the REST API. It makes HTTP requests to endpoints like
/wp-json/wp/v2/posts. It expects the response body to be valid JSON and the HTTP status code to indicate success
(usually 200/201).

When it doesn’t get that—because the response is HTML, empty, truncated, redirected, blocked, or served from the wrong host—it throws
the generic “Invalid JSON response” message. That message is basically WordPress saying: “I asked for JSON, and reality disagreed.”

Your job is to figure out what reality returned instead. That’s always visible in one place: the network response for the REST call
(status code, headers, body). If you’re guessing, you’re wasting time.

One quote worth keeping around when debugging these: paraphrased idea — Charity Majors: “You can’t improve what you don’t measure.”
The editor error is not a measurement. The HTTP exchange is.

Fast diagnosis playbook (first/second/third checks)

First: confirm the failing request and what came back

  1. Open browser DevTools → Network.
  2. Trigger the save/publish action that fails.
  3. Find the request to /wp-json/ (often /wp-json/wp/v2/posts/<id> or /wp-json/wp/v2/settings).
  4. Read: status code, response body, and any redirects.

Decision: If the response body is HTML (login page, error page, WAF block page), you’re not dealing with “JSON parsing.”
You’re dealing with auth, routing, security filtering, caching, or upstream rewriting.

Second: hit the REST API directly from your workstation

Use curl against a simple endpoint like /wp-json/ and a real endpoint like /wp-json/wp/v2/types/post.
Do it with and without authentication (if needed). This separates “browser/CORS weirdness” from “server is broken.”

Third: bypass layers

WordPress sites rarely have “just WordPress.” They have a CDN, a reverse proxy, a WAF, caching, and a plugin zoo.
Bypass in this order:

  1. Bypass CDN (hit origin directly via hosts file or an origin hostname).
  2. Bypass caching (send Cache-Control: no-cache; disable page cache temporarily).
  3. Disable plugins (target security, cache, and header plugins first).
  4. Switch to a default theme (yes, themes can break REST).

Decision: the moment the REST response becomes proper JSON again, you’ve identified the layer to investigate. Don’t re-enable everything at once.
Move one change at a time. You’re debugging, not performing interpretive dance.

Interesting facts and historical context (why this keeps happening)

  • The REST API wasn’t always core. WordPress REST API capabilities evolved via plugins before becoming part of core, so old ecosystems have “opinions” about it.
  • Gutenberg raised the stakes. The Block Editor leans heavily on REST; classic editor workflows hit fewer endpoints and masked many server issues.
  • “Invalid JSON” often means “HTML happened.” A login form, a 403 error page, or a WAF “Access denied” document is valid HTML and invalid JSON—WordPress reports it the same way.
  • Permalinks settings are operational. Rewriting rules aren’t “just SEO.” A broken rewrite setup can turn API routes into 404s, and the editor loses its mind.
  • CORS became more visible with modern browsers. Tightened browser policies made misconfigured cross-origin headers fail loudly, especially for headless setups.
  • Security plugins love to block REST. Many were designed when REST was “optional”; some still treat /wp-json/ as suspicious traffic.
  • CDNs cache more than you think. Misconfigured caching can store a 301/302/403/404 for API routes and happily serve it to everyone, including admins.
  • HTTP/2 and proxies changed failure shapes. Some edge/proxy combinations produce header and redirect behaviors that confuse clients expecting stable endpoints.
  • Mixed content isn’t dead. Sites that half-migrate to HTTPS can still trigger REST requests to HTTP in odd corners (site URL vs home URL vs reverse proxy headers).

The big failure modes: SSL, CORS, plugins, proxies, caching

1) SSL/TLS and “wrong scheme” (HTTP vs HTTPS)

A classic: your admin page loads over HTTPS, but WordPress generates REST URLs with HTTP (or vice versa).
Browsers may block mixed content, or the request follows redirects and returns HTML.

Common root causes:

  • Site URL/Home URL mismatch in WordPress settings or database.
  • Reverse proxy not forwarding X-Forwarded-Proto correctly; WordPress thinks it’s on HTTP.
  • HSTS and forced HTTPS causing extra redirects or cached redirect loops.
  • Certificate mismatch for the hostname used by REST calls (www vs apex).

2) CORS problems (especially headless or unusual admin origins)

If your admin UI is hosted on one origin (domain/port) and WordPress API on another, the browser enforces CORS.
When CORS fails, you often see the WordPress UI report “Invalid JSON response” because the API call never returns readable JSON to JS.

Typical CORS triggers:

  • Using a different domain for admin than for API (e.g., custom admin host, staging hostnames, nonstandard ports).
  • Missing Access-Control-Allow-Origin or wrong value.
  • Preflight OPTIONS blocked by server/WAF.
  • Credentials mode (cookies) requires Access-Control-Allow-Credentials: true and a non-wildcard origin.

3) Plugins and themes: the “helpful” sabotage

A depressing amount of REST failures are self-inflicted:

  • Security plugins blocking REST endpoints for nonces or rate limits.
  • Caching plugins caching API responses or rewriting headers.
  • Optimization/minification plugins breaking wp-api-fetch or nonce injection.
  • Themes that add output before headers (PHP warnings/notices) corrupting JSON output.

Joke #1: The fastest way to reproduce an “Invalid JSON response” is to install three “must-have” plugins from a blog post written in 2016.

4) Reverse proxies, CDNs, and WAFs: the middleboxes

Reverse proxies and CDNs are great until they decide your REST request needs “improvement”:

  • 301/302 redirects applied to /wp-json/ paths.
  • WAF rules (ModSecurity or managed WAF) blocking POST/PUT with JSON bodies.
  • Header size limits, request body limits, or timeouts cutting off responses.
  • Cache keys ignoring cookies; serving anonymous cached responses to admin calls.

5) Permalinks and rewrites: boring, deadly

When rewrite rules are wrong, REST routes can become 404 or routed to the wrong file. WordPress then returns HTML
(a 404 template, or a redirect), and the editor calls it “Invalid JSON.”

Rewrite issues are common after:

  • Moving from Apache to Nginx without matching rules.
  • Changing the document root or adding a subdirectory install.
  • Enabling multisite, or switching domain mapping.

6) Authentication/nonces and “you got logged out”

WordPress REST endpoints often require a valid nonce for admin actions. If cookies aren’t set correctly (domain, secure flag),
or if a proxy strips headers, REST calls might return 401/403. Sometimes the body is HTML (login redirect).

Practical tasks: commands, outputs, decisions (12+)

The point of these tasks is not to type commands for fun. Each one tells you something specific and forces a decision.
Run them from a shell with access to the server when possible. When not possible, run the client-side variants from your workstation.

Task 1: Confirm the REST root endpoint returns JSON

cr0x@server:~$ curl -sS -D- https://example.com/wp-json/ -o /tmp/wpjson.body | head -n 20
HTTP/2 200
content-type: application/json; charset=UTF-8
x-robots-tag: noindex
vary: Origin
date: Fri, 27 Dec 2025 10:12:05 GMT
cr0x@server:~$ head -c 180 /tmp/wpjson.body
{"name":"Example Site","description":"Just another WordPress site","url":"https:\/\/example.com","home":"https:\/\/example.com","gmt_offset":0,

What it means: 200 + application/json + JSON body is healthy at the base endpoint.
Decision: If this fails (non-200 or HTML), focus on routing/WAF/SSL before touching WordPress internals.

Task 2: Check a real REST route used by Gutenberg

cr0x@server:~$ curl -sS -D- https://example.com/wp-json/wp/v2/types/post -o /tmp/types.body | head -n 20
HTTP/2 200
content-type: application/json; charset=UTF-8
cache-control: no-cache, must-revalidate, max-age=0
date: Fri, 27 Dec 2025 10:12:10 GMT
cr0x@server:~$ jq -r '.slug,.rest_base' /tmp/types.body
post
posts

What it means: Core endpoints are reachable and returning parseable JSON.
Decision: If you get 401/403 here, suspect auth/nonces, WAF, or security plugins. If 404, suspect rewrites/permalinks/proxy routing.

Task 3: Detect redirect loops or scheme confusion

cr0x@server:~$ curl -sS -I -L https://example.com/wp-json/ | egrep -i 'HTTP/|location:'
HTTP/2 301
location: http://example.com/wp-json/
HTTP/1.1 301 Moved Permanently
location: https://example.com/wp-json/

What it means: You have a ping-pong redirect between HTTP and HTTPS.
Decision: Fix proxy headers and WordPress URL settings before anything else. Redirect loops are not “minor.”

Task 4: Verify the TLS certificate matches the host used by REST calls

cr0x@server:~$ echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null | openssl x509 -noout -subject -issuer -dates
subject=CN = example.com
issuer=CN = R3, O = Let's Encrypt, C = US
notBefore=Dec  1 00:00:00 2025 GMT
notAfter=Feb 29 23:59:59 2026 GMT

What it means: Cert is valid for the hostname.
Decision: If CN/SAN doesn’t match (e.g., only www), align canonical host or install a cert covering both.

Task 5: Check WordPress “home” and “siteurl” values (CLI)

cr0x@server:~$ cd /var/www/html
cr0x@server:~$ wp option get home
https://example.com
cr0x@server:~$ wp option get siteurl
http://example.com

What it means: Mismatch. REST URLs may be built from siteurl in some contexts.
Decision: Make them consistent (usually both HTTPS) and ensure the reverse proxy reports HTTPS properly.

Task 6: Fix WordPress URLs safely (CLI)

cr0x@server:~$ wp option update home 'https://example.com'
Success: Updated 'home' option.
cr0x@server:~$ wp option update siteurl 'https://example.com'
Success: Updated 'siteurl' option.

What it means: Canonical URLs now match.
Decision: Re-test /wp-json/ and a Gutenberg save. If redirects persist, the proxy/CDN layer is still rewriting.

Task 7: Verify rewrite rules are present (WordPress flush)

cr0x@server:~$ wp rewrite list --fields=match,query | head
^wp-json/?$	index.php?rest_route=/
^wp-json/(.*)?	index.php?rest_route=/$matches[1]
^index\.php/wp-json/?$	index.php?rest_route=/

What it means: WordPress knows about REST rewrites.
Decision: If these are missing, flush permalinks. If present but requests still 404, the web server rewrite rules are wrong.

Task 8: Confirm Nginx routes /wp-json correctly

cr0x@server:~$ sudo nginx -T 2>/dev/null | sed -n '1,120p'
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
user www-data;
worker_processes auto;
http {
    server {
        listen 443 ssl http2;
        server_name example.com;
        root /var/www/html;

        location / {
            try_files $uri $uri/ /index.php?$args;
        }
    }
}

What it means: The standard try_files fallback is present.
Decision: If you see a conflicting location block that intercepts /wp-json (or returns static 404), fix it.

Task 9: Check Apache .htaccess rewrite rules (if Apache)

cr0x@server:~$ sudo sed -n '1,120p' /var/www/html/.htaccess
# BEGIN WordPress
RewriteEngine On
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
RewriteBase /
RewriteRule ^index\.php$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php [L]
# END WordPress

What it means: Baseline WordPress rewrites are intact.
Decision: If RewriteEngine is off or the file is ignored (AllowOverride), REST routes can fail as 404. Fix vhost config accordingly.

Task 10: Spot WAF/ModSecurity blocks in logs

cr0x@server:~$ sudo egrep -R "ModSecurity|Access denied|403" /var/log/apache2/ | tail -n 8
/var/log/apache2/error.log:[Wed Dec 25 09:41:12.123456 2025] [security2:error] [pid 1822] [client 203.0.113.10] ModSecurity: Access denied with code 403 (phase 2). Match of "rx (?i:select.+from)" against "ARGS:content" required. [id "942100"]

What it means: A rule is blocking content in a REST request.
Decision: Exclude the REST endpoints from that rule set or tune rules for authenticated admin paths. Don’t “just disable security” globally.

Task 11: Identify whether the response is HTML (login page) instead of JSON

cr0x@server:~$ curl -sS -D- https://example.com/wp-json/wp/v2/posts -o /tmp/posts.body | head -n 15
HTTP/2 401
content-type: text/html; charset=UTF-8
date: Fri, 27 Dec 2025 10:12:55 GMT
cr0x@server:~$ head -n 5 /tmp/posts.body




What it means: WordPress expected JSON but got HTML, likely a login/auth response or a proxy-generated page.
Decision: Fix authentication headers/cookies/nonces or the proxy rule that rewrites 401/403 into an HTML page.

Task 12: Validate that OPTIONS preflight isn’t blocked (CORS)

cr0x@server:~$ curl -sS -D- -X OPTIONS https://example.com/wp-json/wp/v2/posts \
  -H 'Origin: https://admin.example.com' \
  -H 'Access-Control-Request-Method: POST' \
  -H 'Access-Control-Request-Headers: content-type,x-wp-nonce' \
  -o /dev/null | head -n 25
HTTP/2 403
content-type: text/html; charset=UTF-8
date: Fri, 27 Dec 2025 10:13:20 GMT

What it means: Preflight is blocked. The browser will refuse the actual request.
Decision: Adjust server/WAF to allow OPTIONS and return correct CORS headers for REST routes.

Task 13: Check whether a CDN is caching API responses

cr0x@server:~$ curl -sS -D- https://example.com/wp-json/ -o /dev/null | egrep -i 'cache|age|cf-|x-cache'
cache-control: max-age=14400
age: 8732
x-cache: HIT

What it means: Something is caching the REST root for hours.
Decision: Exclude /wp-json/* from CDN/page caching or set Cache-Control: no-store for authenticated REST endpoints.

Task 14: Reproduce as the PHP runtime sees it (local curl to origin)

cr0x@server:~$ curl -sS -D- http://127.0.0.1/wp-json/ -H 'Host: example.com' -o /tmp/origin.body | head -n 20
HTTP/1.1 200 OK
Content-Type: application/json; charset=UTF-8

What it means: Origin is fine; something in front is breaking it.
Decision: Fix proxy/CDN/WAF rules instead of touching WordPress settings.

Task 15: Find PHP warnings corrupting JSON output

cr0x@server:~$ sudo tail -n 30 /var/log/php8.2-fpm.log
[27-Dec-2025 10:11:41] WARNING: [pool www] child 2199 said into stderr: "PHP Warning:  Undefined array key "foo" in /var/www/html/wp-content/themes/custom/functions.php on line 412"
[27-Dec-2025 10:11:41] WARNING: [pool www] child 2199 said into stderr: "PHP Warning:  Cannot modify header information - headers already sent by (output started at /var/www/html/wp-content/themes/custom/functions.php:412)"

What it means: Output occurred before headers; REST response can be corrupted (JSON preceded by warnings).
Decision: Fix the theme/plugin emitting output. In production, don’t rely on “hide warnings”; eliminate them.

Task 16: Quick plugin isolation without uninstalling (CLI)

cr0x@server:~$ wp plugin list --status=active
+----------------------+----------+-----------+---------+
| name                 | status   | update    | version |
+----------------------+----------+-----------+---------+
| wordfence            | active   | none      | 7.11.0  |
| wp-rocket            | active   | none      | 3.16.0  |
| redis-cache          | active   | none      | 2.5.3   |
| custom-mu-plugin     | active   | none      | 1.0.0   |
+----------------------+----------+-----------+---------+
cr0x@server:~$ wp plugin deactivate wordfence wp-rocket
Plugin 'wordfence' deactivated.
Plugin 'wp-rocket' deactivated.

What it means: You can test REST behavior with fewer moving parts.
Decision: If the problem disappears, re-enable one at a time. If it doesn’t, don’t blame plugins out of habit—move on to proxy/SSL/routing.

Joke #2: “Invalid JSON response” is WordPress’s way of telling you your infrastructure has opinions—and it’s in a bad mood.

Common mistakes: symptom → root cause → fix

1) Symptom: Editor fails to publish; Network shows 301/302 then HTML

Root cause: Scheme/host canonicalization redirecting REST endpoints (HTTP↔HTTPS, www↔apex), sometimes looping.

Fix: Align home and siteurl to the canonical HTTPS host; fix proxy headers (X-Forwarded-Proto), and stop rewriting /wp-json differently than normal page traffic.

2) Symptom: REST request returns 403 with a “blocked” HTML page

Root cause: WAF/ModSecurity rule triggered by JSON payload, post content, or REST route patterns.

Fix: Tune WAF: allow authenticated REST routes, permit OPTIONS, and scope exceptions narrowly to /wp-json/ and admin users. Verify in logs.

3) Symptom: REST request returns 401; body looks like login page

Root cause: Authentication/nonces failing due to cookie domain/secure flags, caching mixing anonymous/admin, or proxy stripping headers.

Fix: Ensure admin cookies are secure over HTTPS, exclude REST from page caching, and confirm Authorization header forwarding (esp. with Apache/Nginx + PHP-FPM).

4) Symptom: REST endpoint returns 404, but site pages work

Root cause: Rewrite rules for WordPress aren’t applied (bad Nginx config, .htaccess ignored), or a conflicting location rule intercepts /wp-json.

Fix: Correct web server routing to pass unknown paths to index.php; flush permalinks; verify /wp-json/ returns JSON.

5) Symptom: Works on origin, fails through CDN

Root cause: CDN caching or security policy differences at the edge. Sometimes the edge injects redirects or blocks POST requests.

Fix: Bypass the CDN to confirm; then configure edge rules to not cache /wp-json/*, allow REST methods, and forward needed headers/cookies.

6) Symptom: Random failures; sometimes “invalid JSON”, sometimes OK

Root cause: Intermittent upstream timeouts, PHP-FPM saturation, or caching race conditions returning partial/truncated responses.

Fix: Check upstream timeout settings and PHP-FPM health; increase limits carefully; fix slow plugins/DB; stop caching admin and REST responses.

7) Symptom: Response is JSON but editor still errors

Root cause: JSON is malformed (PHP warnings injected), or the response is compressed/truncated by a proxy; sometimes charset or BOM issues appear from custom code.

Fix: Hunt PHP warnings/notices, disable output buffering hacks, and confirm the raw response body starts with { or [ and is complete.

8) Symptom: Only one admin user sees it; others don’t

Root cause: Browser extensions, stale cookies, different network path (VPN), or a per-user WAF/rate-limit trigger.

Fix: Compare headers and responses across users; test in a clean browser profile; check WAF logs keyed by IP/user-agent.

Three corporate mini-stories from real operations life

Mini-story 1: The incident caused by a wrong assumption

A marketing team rolled out a “tiny” domain change: move the blog from blog.example.com to www.example.com/blog.
The migration plan was mostly about redirects and SEO. Everyone agreed the editor would keep working because “it’s the same WordPress instance.”

The first sign of trouble wasn’t a 500 error or a site outage. It was editors unable to publish. “Invalid JSON response” popped up
when saving posts, but the frontend rendered fine. The assumption was that the REST API would follow the same redirects as the browser.
It did follow them—right into a login page from a different cookie scope.

The REST calls were being redirected from /blog/wp-json/ to /wp-json/ at the edge, and the cookies set for
blog.example.com weren’t sent to www.example.com. So WordPress treated authenticated requests as anonymous,
returned HTML login, and Gutenberg reported JSON invalid.

Fixing it was not heroic: set consistent canonical URLs (home/siteurl), stop redirecting REST endpoints differently than normal paths,
and align cookie domains. What hurt was the delay—hours spent “debugging WordPress” when the problem was a basic origin/host consistency issue.

Mini-story 2: The optimization that backfired

A platform team decided to “speed up WordPress” by caching more aggressively at the CDN. The rule was simple: cache everything that doesn’t look like admin.
Someone added an exception for /wp-admin and called it a day.

Except Gutenberg doesn’t live only in /wp-admin. It talks to /wp-json/ constantly, and those requests carry cookies and nonces.
The CDN, not understanding WordPress semantics, cached a 403 response from an early request that hit a rate-limit rule. Then it served that 403 to everyone.
The editor error showed up across multiple sites and environments. The frontend was still fine—caches are great at hiding problems until they’re great at amplifying them.

The rollback was immediate: purge cache, disable caching for /wp-json/*, and stop caching responses when cookies are present.
Then came the uncomfortable postmortem detail: the team optimized without an explicit list of “must never cache” paths.

The lasting fix was boring: a shared caching policy with explicit exclusions for REST endpoints and admin flows, plus automated checks
that validate /wp-json/ returns application/json and a non-cached response. Performance improved later, but it was earned carefully.

Mini-story 3: The boring but correct practice that saved the day

A company with multiple WordPress properties had a habit that looked like overkill: a tiny synthetic probe that ran every five minutes.
It fetched /wp-json/ and a representative authenticated endpoint, and it failed loudly if the content-type wasn’t JSON or if the response body contained <html.

One morning, the probe triggered right after a routine security rule update. Editors hadn’t complained yet, but the system already knew.
The operations team looked at the WAF logs and found OPTIONS requests being blocked for /wp-json, which would break the editor in certain browsers and networks.

Because the probe recorded the exact status code and response headers, the team didn’t debate. They deployed a narrow exception:
allow OPTIONS and POST to /wp-json/ for authenticated sessions, keep the rest of the rules intact.

Nobody wrote a dramatic Slack thread about it. The editor never went down for most users. The best operational work often looks like nothing happened,
which is the highest compliment you’ll never receive.

Checklists / step-by-step plan

Step-by-step triage (15–30 minutes)

  1. Capture the failing REST call in DevTools: URL, status code, response body (first 200 bytes), and any redirects.
  2. Test /wp-json/ with curl and confirm application/json content-type and valid JSON.
  3. Test a real endpoint like /wp-json/wp/v2/types/post. If that fails, don’t bother with Gutenberg-specific theories yet.
  4. Check for redirects with curl -I -L. Kill redirect loops first.
  5. Bypass the CDN (origin direct) and compare results. If origin works, stop debugging WordPress and start debugging the edge.
  6. Check server/WAF logs for 401/403 blocks around the time of your test.
  7. Temporarily disable “high-risk” plugins: security, cache, minify, header/CORS managers. Re-test.
  8. Check PHP logs for warnings that could corrupt JSON responses.
  9. Confirm rewrite rules (Nginx try_files / Apache AllowOverride + .htaccess). Re-test.

Stabilization checklist (same day)

  • Exclude /wp-json/* from page caching/CDN caching unless you truly understand the implications.
  • Allow REST methods: GET/POST/PATCH/PUT/DELETE and OPTIONS where appropriate. Block by auth, not by path alone.
  • Standardize canonical host and scheme (HTTPS). Set home and siteurl consistently.
  • Ensure reverse proxy sets X-Forwarded-Proto and the app trusts it (or terminates TLS at WordPress).
  • Fix PHP warnings/notices in production code. “It’s only a warning” is how you get invalid JSON at 3 p.m.
  • Put a basic probe in place: fetch /wp-json/ and validate JSON + content-type.

Prevention checklist (this month)

  • Define “non-cacheable routes” as a policy (not tribal knowledge): REST, admin, login, cart/checkout if applicable.
  • Test edge/WAF changes against REST endpoints before rollout.
  • Keep a minimal plugin set; prefer fewer, well-maintained plugins over stacked “optimization” layers.
  • Version-control proxy config and WordPress environment settings; treat them as production code.
  • Instrument: track HTTP status codes for /wp-json/*, and alert on spikes in 401/403/5xx.

FAQ

1) Is “Invalid JSON response” always a WordPress bug?

No. It’s usually WordPress being honest about a failed HTTP request. Most cases are SSL redirects, WAF blocks, caching, or plugin interference.
Start with the network response, not the WordPress UI.

2) Why does the frontend work while the editor fails?

Frontend pages can render from cached HTML and tolerate redirects. The editor needs REST endpoints, correct auth, and JSON responses.
You can have a perfect-looking site with a broken control plane.

3) What’s the single fastest test?

Fetch /wp-json/ and verify you get 200 and Content-Type: application/json, plus a JSON body.
If you get HTML, you have a routing/auth/security layer problem.

4) Can CORS cause this even on a normal WordPress site?

Yes, if the admin interface or API requests originate from a different domain/port (custom admin hostnames, headless setups, staging behind a different origin).
CORS failures can surface as “invalid JSON” because JS can’t read the response.

5) Do caching plugins commonly break REST?

They can. Good ones try not to, but misconfiguration is common. If a caching layer caches /wp-json or ignores cookies, you’ll get wrong responses
served to authenticated requests. Exclude REST routes from page cache unless you’re very deliberate.

6) Why does disabling a security plugin fix it?

Some security plugins block REST endpoints to reduce attack surface. That’s not inherently wrong, but it’s often too aggressive for Gutenberg.
Configure allowlists for authenticated users and required endpoints instead of blanket blocks.

7) Could this be a storage or filesystem issue?

Indirectly. If the system is under I/O pressure and PHP times out, REST requests can fail or return partial responses.
But “Invalid JSON” is rarely the first symptom of a disk issue; you’ll usually see timeouts, 502/504, and slow admin overall.

8) What if REST works with curl but fails in the browser?

That points to CORS, cookies/nonces, or browser-only layers (extensions, mixed content blocking, strict transport policies).
Compare request headers between curl and the browser; check DevTools Console for CORS or mixed content errors.

9) Does switching permalink settings help?

Sometimes. “Save permalinks” forces a rewrite flush, which can fix missing rules after migrations.
If the web server routing is wrong, though, no amount of WordPress flushing will fix Nginx/Apache config.

10) What’s the safest production approach to debugging?

Avoid random toggling. Capture the failing request, test origin vs edge, and isolate one change at a time.
If you disable plugins, do it briefly and during low-impact windows. Better: replicate on staging with production-like proxy rules.

Next steps you should actually take

If you’re in the middle of an incident, do the fast playbook: inspect the failing REST request, curl /wp-json/, and bypass the CDN.
That will tell you which layer is guilty within minutes.

Then fix what’s proven broken:

  • If you see redirects or scheme flips: align WordPress URLs, correct proxy headers, stop special-casing /wp-json.
  • If you see 403/401 with HTML bodies: tune WAF/security plugins, exclude REST from caching, ensure auth headers/cookies work.
  • If you see 404: repair rewrites in Nginx/Apache and flush permalinks after.
  • If you see malformed JSON: eliminate PHP warnings/notices and output before headers from themes/plugins.

Finally, prevent the repeat: add a small synthetic probe for REST health, codify caching exclusions, and treat proxy/WAF changes like deployments.
WordPress is not fragile. It’s just surrounded by layers that are very confident and occasionally wrong.

← Previous
Docker “volume is in use”: remove or replace without data loss
Next →
AMD Adrenalin: when software features matter more than silicon

Leave a comment