“Updating failed. The response is not a valid JSON response” means the block editor sent your save to the REST API and got back something that was not JSON — an HTML error page, a PHP warning, a firewall block page, or an empty body. Your post is not corrupt and the problem is usually not in WordPress itself: it is in whatever sits between the editor and wp-json.
Every list of fixes for this error tells you to resave permalinks, rename .htaccess and disable your plugins. One of those may work by accident. The faster route is that the failing response already names the cause, and reading it takes about thirty seconds.
The thirty-second version
F12), go to Network, filter to Fetch/XHR, then press Update. Click the failed request to wp-json and open its Response tab. HTML means something intercepted the request. A Warning: line above the JSON means a plugin is printing before the response. An empty body means PHP died. Each of those points at a different fix.What the response is not a valid JSON response actually means
The block editor does not post a form. It saves over the REST API: a POST to /wp-json/wp/v2/posts/123, sent through the @wordpress/api-fetch package, which parses the reply with response.json(). When that parse throws, api-fetch has no useful detail to show you, so it falls back to the generic string you are reading.
So the message is about the shape of the reply, not the content of your post. That distinction rules things out quickly. A permissions failure returns perfectly valid JSON with an error code in it, and the editor shows that message instead — “Sorry, you are not allowed to edit this post.” An expired login returns JSON too. Seeing the JSON message means the body was not JSON at all.
The same mechanism produces three wordings people report as separate bugs: Updating failed, Publishing failed, and the quieter autosave notice. Image uploads fail the same way, because media goes through wp/v2/media on the same route.
This message is on its way out
wp/7.2 branch, replaces the generic text with the HTTP status: an HTML error page now reports Error code 502. instead, a REST error reports Error code 403. followed by the server’s own message, and a dropped connection says the autosave failed because there was no response from the server. On WordPress 7.1 you still have to read the response yourself, which is what the rest of this guide does.Read the response before you change anything
The Network tab is the primary tool, because it shows the exact request that failed. Three shell commands fill in the rest. An unauthenticated GET to the REST index is public on a default install, so you can run these from your own machine:
# 1. Does the REST index answer at all, and with what content type?
curl -sS -o /dev/null -w '%{http_code} %{content_type}\n' \
https://example.com/wp-json/
# 2. What is actually in the body? The first few hundred bytes decide it.
curl -sS https://example.com/wp-json/wp/v2/types | head -c 600
# 3. Same route, bypassing pretty permalinks entirely
curl -sS -o /dev/null -w '%{http_code} %{content_type}\n' \
'https://example.com/?rest_route=/wp/v2/types'
Read the content type first. application/json; charset=UTF-8 means WordPress answered. text/html means something else did, and command two tells you what. If command one fails and command three succeeds, the route is not being handed to WordPress — a rewrite problem, not a WordPress problem. The rest_route form is not a hack, incidentally: the REST API handbook documents it as the supported way to call a route when pretty permalinks are off.
A clean curl does not clear the server
POST carrying cookies, a nonce and your post body. A firewall rule that only inspects request bodies, or only trips on POST, will let all three commands above pass and still block the save. If curl looks healthy and the editor still fails, trust the Network tab over curl.What came back, and where to fix it
| What is in the response | What it is | Where the fix lives |
|---|---|---|
HTML beginning <!DOCTYPE html>, showing your theme’s 404 page | WordPress received the request but has no route for that URI | Permalinks and rewrite rules |
<br /><b>Warning</b>: or Notice:, then valid-looking JSON after it | Something printed to the output buffer before the REST response | The plugin or theme printing it, plus debug display |
| A branded “Access denied” or “Request blocked” page, status 403 | A web application firewall, mod_security, or a security plugin | Host firewall rules or the plugin’s allowlist |
| A 502 or 504 gateway page from the host | PHP died or never answered: memory exhausted, or the request timed out | Memory limit, execution time, or a slow hook on save |
| An nginx or Apache default 404, no WordPress styling | The web server never passed the URI to index.php | Server config, not WordPress |
| Status 200 with a completely empty body | A fatal error with display off, or output buffering that swallowed everything | debug.log |
| A 301 or 302 to a different host or scheme | Site address mismatch, an HTTPS or www redirect catching the API | siteurl and home, and redirect rules |
JSON containing "code":"rest_cookie_invalid_nonce" | Not this error. The body is valid JSON; your login expired | Reload the editor and sign in again |
The causes, in the order they actually turn up
If the response body does not obviously match one of these, isolate in the ordinary way: deactivate plugins in halves, reload the editor and try a save between each step. Troubleshooting a WordPress plugin conflict step by step covers doing that on a live site, and disabling a plugin without dashboard access is the version for when the admin is unusable too.
Fixing each cause
Rewrite rules and permalinks
Visiting Settings → Permalinks and pressing Save Changes flushes the rules and rewrites the WordPress block in .htaccess when the file is writable. From the shell:
wp rewrite structure '/%postname%/' --hard
wp rewrite flush --hard
# Confirm the API base the editor will use
wp eval 'echo rest_url() . PHP_EOL;'
If .htaccess is not writable, nothing is written and the setting appears to save anyway, so check the file afterwards. The same missing fallback produces front-end 404s on every post but the home page, which is why fixing 404 errors in WordPress and fixing this error are frequently the same job. On nginx, .htaccess is ignored entirely and the try_files directive is the thing to check.
Stray output ahead of the JSON
Two changes, in this order. First stop the warnings reaching the browser, which fixes the symptom immediately; then find what is emitting them. In wp-config.php, above the /* That's all, stop editing! */ line:
define( 'WP_DEBUG', true );
define( 'WP_DEBUG_LOG', true );
define( 'WP_DEBUG_DISPLAY', false );
@ini_set( 'display_errors', 0 );
That sends notices to wp-content/debug.log instead of into your API responses. Save the post again, then read the tail of the log: whatever was printed into the response is now written there with a file and line number. Enabling WordPress debugging and reading error logs safely covers doing this on a production site without exposing paths to visitors.
A production site should never have display_errors on in the first place, so treat this as two findings: a plugin bug to report, and a server configuration to correct.
Firewall and mod_security rules
Confirm it before you ask anyone to change anything. Save a trimmed copy of the post with the suspicious content removed — delete the Custom HTML block, or the code sample containing a SELECT. If the trimmed version saves and the full one does not, a content-inspecting rule is firing.
Then take the failing request’s timestamp and response to your host and ask which rule matched. Managed hosts can read the audit log and whitelist a rule for authenticated POST requests to /wp-json/, which is a targeted change. Turning the firewall off site-wide is not; if a security plugin’s own firewall module is responsible, allowlist your own user or the REST route rather than disabling protection you still need.
Timeouts and memory on save
A 502 or 504 is a server-side death, not an editor problem. Raise the ceiling enough to confirm the diagnosis, then find what is spending the budget:
# What are the limits now?
wp eval 'echo ini_get("memory_limit"), " / ", ini_get("max_execution_time"), PHP_EOL;'
# Which plugins hook save_post, and in what order?
wp eval 'global $wp_filter; foreach ( $wp_filter["save_post"] as $priority => $hooks ) {
foreach ( $hooks as $h ) {
$cb = $h["function"];
echo $priority, " ", is_array( $cb )
? ( is_object( $cb[0] ) ? get_class( $cb[0] ) : $cb[0] ) . "::" . $cb[1]
: ( is_string( $cb ) ? $cb : "closure" ), PHP_EOL;
}
}'
That list is usually the answer on its own: a sitemap regenerator or an image optimiser running synchronously on every save is the common culprit, and most have a setting to defer the work. Fixing the allowed memory size exhausted error covers raising the limit properly, and diagnosing a slow WordPress admin dashboard covers finding the plugin that is costing you the seconds.
Site address and HTTPS
wp option get siteurl
wp option get home
Both should match the scheme and host you actually use in the browser, including whether www is present. Fixing a mismatch is not a one-line change — hard-coded URLs in content, in wp-config.php and in a CDN config all have to agree — so follow changing your WordPress site URL safely rather than editing the option and hoping.
Caching and the CDN
Exclude /wp-json/ from page caching and from HTML minification, and make sure “cache everything” rules do not cover it. Most caching plugins already bypass REST requests for logged-in users; edge rules written by hand often do not. Test by loading the editor over a connection that bypasses the CDN — if the save succeeds there, you have your answer without changing a single plugin setting.
What Site Health does and does not tell you
Tools → Site Health → Status runs a REST availability check, and its exact wording distinguishes three different failures. Core sends a GET to wp/v2/types/post with context=edit, an X-WP-Nonce header and a ten-second timeout, then checks for a 200 whose JSON contains a capabilities key:
Site Health can disagree with your browser, legitimately
The rest of that screen is still worth a look while you are there — finding and fixing common problems with WordPress Site Health goes through the checks that matter and the ones you can ignore.
Getting the post out while you debug
Your content is still in the browser, and nothing has been lost. Before you reload anything, open Options → Code editor (Ctrl+Shift+Alt+M), select everything and paste it into a local file. That is the whole post as block markup, and it will restore cleanly into any editor later.
Then stop pressing Update. Repeated failed attempts queue autosaves and can leave a post lock behind, which turns a save problem into an “another user is editing this post” problem on top of it.
If you must publish before the cause is found, the Classic Editor plugin saves through post.php rather than the REST API, so it can get a post out while /wp-json/ is unreachable. Treat it as a bypass and not a fix: the API is still broken for media uploads, the Site Editor and anything else built on it. A smaller version of the same trick is to split a long post — if a firewall is objecting to one block, publishing without it and adding it afterwards tells you which block is the problem.
Do not paste a stale copy over a good one
Keeping it from coming back
Frequently asked questions
Will I lose the post I was editing?
Not if you copy it out before reloading. Open Options → Code editor, select everything and paste it into a text file. The block markup you get is the complete post and restores cleanly later. Reloading the tab without doing that is what loses work, because the failed save means nothing was written.
Why does it only happen on one particular post?
Because the cause depends on the request, not the site. A firewall rule inspecting the request body fires on the post containing a script tag or a SQL sample. A timeout fires on the longest post. Both look site-wide until you notice the short ones still save. Try saving a new empty draft: if that works, the content of the failing post is part of the cause.
Is resaving permalinks really a fix, or just folklore?
It is a real fix for exactly one cause: rewrite rules that no longer route /wp-json/, which is common after a migration. It does nothing for a firewall, a timeout or a plugin printing warnings. It is harmless to try, which is why it tops every list, but if it does not work the first time it will not work the fifth.
Should I disable the REST API to stop this happening?
No, and doing so causes this error rather than preventing it. The block editor, the Site Editor, media uploads and Site Health all run on the REST API. Plugins that “disable” it generally restrict unauthenticated access, which is a reasonable hardening choice; one that blocks authenticated requests breaks editing entirely. If you installed something like that recently, it is the first thing to test.
How is this different from a block showing invalid content?
They are unrelated. Invalid content is a client-side check comparing stored markup with a block’s current save output, and it never involves the network. This error is the network request failing. Fixing a block that contains unexpected or invalid content covers the other one. Seeing both at once usually means a plugin update changed block markup and broke REST output in the same release.
The response is not a valid JSON response only when I upload images. Why?
Uploads hit wp/v2/media with a much larger body and do real work on arrival: generating sizes, running an optimiser, sometimes shipping the file to object storage. That makes them the first requests to hit a post_max_size or upload_max_filesize ceiling, a memory limit or a timeout, each of which returns an HTML error page rather than JSON. Try a 50 KB image: if that uploads and a 4 MB one does not, it is a size or resource limit, not the API.
Does this error affect visitors?
Often not, which is why it can go unnoticed for weeks. But the underlying cause sometimes does: broken rewrite rules also 404 your posts, and a save-path timeout usually means slow pages too. Check the front end once rather than assuming the problem is confined to the admin.
Sources
- REST API Key Concepts in the WordPress developer handbook — the
/wp-json/route structure and the documented?rest_route=form for sites without pretty permalinks WP_Site_Health::get_test_rest_availability()— the request Site Health makes, thecapabilitiescheck, and the three result labels quoted above- Gutenberg pull request 85157 — the merged change that replaces this message with the HTTP status, in the 24.3 milestone and pinned to the
wp/7.2branch - Gutenberg issue 61671 — a real report narrowed to a page builder, with the PHP notice in
debug.logthat matches the stray-output pattern - Core ticket 47416 — Site Health reporting a REST API error from a loopback cURL timeout rather than a genuine API fault

Leave a Reply to This Post