Key facts at a glance
The publishing-failed error in 2026
Last updated
- What it really is
- Not a JSON bug. The block editor saves your post through the WordPress REST API, and the error means that save request came back as something other than clean JSON: an HTML error page, a redirect, a 403, a login page, or JSON with stray text in front.
- Updating vs Publishing
- Same root cause, two labels.
Updating failedshows when you save an existing post,Publishing failedwhen you publish a new one. Diagnose them identically. - The 60-second test
- Visit
your-site.com/wp-json/. Clean JSON means REST is reachable, look at nonces, input limits, and PHP notices. A 403, redirect, login, or HTML error means REST is blocked, look at security plugins, permalinks, and SSL. - The most common cause
- A security plugin or firewall blocking the REST API. Wordfence, iThemes, All In One WP Security, Cloudflare, and Sucuri all do it, because a stream of
wp-jsonrequests can look like abuse. The number one cause in real audits. - Site Health names it
- Tools then Site Health flags the related failures directly: a REST API error, your site could not complete a loopback request, or an active PHP session that interferes with REST and loopback requests.
- The two quiet causes people miss
- First, a caching layer serving a stale nonce: the REST API treats a request with a missing or expired nonce as unauthenticated, so it fails. Second, a PHP warning leaking into the response: with display_errors on, a plugin notice, increasingly a PHP 8.4 deprecation, prints characters before the JSON and the editor cannot parse it. Both pass the /wp-json/ test yet still break the save.
Source: the WordPress REST API handbook and authentication reference, the WordPress Site Health documentation, the WordPress debugging documentation, the WordPress permalink settings reference, and our hands-on repairs. Get a quote in 60 seconds →
What the error really means
The modern WordPress block editor does not save your post the old way, by reloading the whole admin page. It saves in the background by sending your content to the WordPress REST API, a set of addresses under your-site.com/wp-json/ that accept and return data as JSON. When you click Publish or Update, the editor packages your post, sends it to the right /wp-json/ endpoint, and waits for a small JSON reply confirming the save and returning the updated post. That round trip is the entire save mechanism.
The error appears when that reply is not the clean JSON the editor expects. If a security plugin returns a 403, the editor gets a block page. If permalinks are broken, the request 404s. If the site redirects between HTTP and HTTPS, the editor follows a redirect to an HTML page. If a PHP warning prints to the screen, the response is text followed by JSON. In every case the editor tries to read JSON, fails, and reports The response is not a valid JSON response. The phrase describes the editor's disappointment, not the disease. The disease is whatever stopped a clean JSON reply from coming back.
There is one more layer worth knowing, because it explains the quiet cases. REST requests from the editor are authenticated with a short-lived security token called a nonce, sent in an X-WP-Nonce header. The WordPress documentation is explicit that if no valid nonce is provided, the API treats the request as unauthenticated even when you are logged in. That is why a cached page serving a stale nonce can break saving while the REST API itself looks perfectly healthy. Understanding these three things, the REST round trip, the requirement for a clean JSON reply, and the nonce, makes the whole diagnosis fast.
- Routing: the
/wp-json/route must resolve. Permalinks and rewrite rules. - Access: nothing, no security plugin, firewall, or server rule, may block the request.
- Authentication: a valid, fresh nonce must reach the API. Caching can spoil it.
- Cleanliness: the reply must be only JSON, with no PHP warning or stray output in front.
The 60-second test that splits every case
Before changing anything, run this one test. It decides which half of the causes you are in and saves you from guessing.
Open a new tab and go to your-site.com/wp-json/
You see a wall of JSON
The REST API is reachable. The block is in the authenticated save itself. Focus on:
- →A stale nonce from caching
- →max_input_vars on long, block-heavy posts
- →A PHP notice contaminating the save response
You see a 403, login, redirect, or HTML error
The REST API is blocked or misrouted. Focus on:
- →A security plugin or firewall blocking wp-json
- →Broken permalinks or .htaccess rewrite rules
- →An SSL or WordPress Address and Site Address mismatch
Which symptom matches yours
Find the row that matches your situation. Each one narrows to a specific cause and fix.
| Symptom | Most likely root cause |
|---|---|
| Updating failed or Publishing failed, the response is not a valid JSON response | The editor save request to the REST API came back as something other than clean JSON. The umbrella symptom for every cause below. Start with the 60-second /wp-json/ test. |
| Visiting your-site.com/wp-json/ shows a 403, a login page, or an HTML error instead of JSON | The REST API is blocked or misrouted. Most often a security plugin or firewall blocking wp-json, or broken permalink rewrite rules. This is the larger of the two camps. |
| The error started after installing or updating a security plugin, or behind Cloudflare or Sucuri | A security plugin or web application firewall is treating REST API requests as suspicious and blocking them. The single most common cause in real audits. |
| The error started after a migration, a host move, or editing .htaccess | Broken rewrite rules so /wp-json/ routes no longer resolve, or a WordPress Address and Site Address mismatch. Re-save permalinks and check the URL settings. |
| Site Health shows a loopback request could not complete, or you see cURL error 28 | REST and loopback requests are timing out. A firewall blocking the site calling itself, a DNS issue, limited resources, or outdated PHP, cURL, or OpenSSL. |
| It only fails on long or block-heavy posts, short ones save fine | A PHP input limit. max_input_vars defaults to about 1000 and a complex post exceeds it, truncating the save. Raise it to 3000 or more. |
| The error comes and goes, or appears after the editor has been open a while | A stale nonce from caching. A cached admin page serves an expired token and the REST API treats the save as unauthenticated. Exclude wp-admin and /wp-json/ from caching. |
| /wp-json/ returns valid JSON but saving still fails, and debug.log shows a PHP notice | A plugin or theme is printing output or a PHP warning into the response, contaminating the JSON. Common with PHP 8.4 deprecations. Turn off display_errors and fix the source. |
The six root causes
Every publishing-failed case is one of these six. The first three block the REST API and fail the 60-second test. The last three break the authenticated save while the REST API still looks healthy.
1. A security plugin or firewall blocking the REST API
This is the most common cause in real audits. Security plugins such as Wordfence, iThemes Security, and All In One WP Security, and site-level firewalls like Cloudflare and Sucuri, often restrict or block REST API requests, because a stream of requests to wp-json can resemble an attack. When the firewall returns a 403 or a challenge page instead of letting the save through, the editor gets a non-JSON response and fails. Confirm it by temporarily disabling the security plugin or pausing the firewall and trying again. Once confirmed, do not leave protection off. Find the blocked request in the firewall log and add a rule that allows REST API access for logged-in users, keeping the security while unblocking the editor.
2. Broken permalinks or .htaccess rewrite rules
The REST API is served at pretty URLs under /wp-json/, and those routes depend on the same rewrite rules that power your permalinks. When the rewrite rules or the .htaccess file get corrupted, common after a migration, a server move, or a manual edit, the /wp-json/ routes stop resolving and every REST request 404s. The fix is quick: go to Settings then Permalinks and click Save Changes, which forces WordPress to regenerate the rewrite rules and the .htaccess block without changing your URL structure. If .htaccess is not writable, restore the default WordPress rewrite block by hand.
3. An SSL or site-URL mismatch
If the WordPress Address and the Site Address do not match, or the site redirects between http and https inconsistently, the editor's REST request can be redirected to a different URL and arrive as an HTML page rather than a JSON endpoint. Mixed content, where an HTTPS page loads insecure HTTP resources, can also break REST authentication silently. Confirm that the WordPress Address and Site Address in Settings then General match and both use https, that your SSL certificate is valid, and that the site is not bouncing requests through an extra redirect on the way to /wp-json/.
4. A failed loopback or cURL error 28 timeout
WordPress makes loopback requests, calling its own site over HTTP, for scheduled events, the built-in editors, and REST features. When those time out you get cURL error 28, and Site Health reports that your site could not complete a loopback request. The save never gets its confirmation and the editor fails. Causes include a firewall blocking the site from calling itself, a DNS problem, limited server resources, or outdated server software. WordPress recommends PHP 7.4.13 or newer, cURL 7.74.0 or newer, and OpenSSL 1.1.1 or newer, so ask your host to update if you are below those. An active PHP session left open also interferes with REST and loopback requests, as Site Health notes directly.
5. A PHP input limit on long or complex posts
When the error appears only on big posts and short ones save fine, the cause is a PHP input ceiling. The max_input_vars setting caps how many variables a single request may carry, and it defaults to around 1000 on many hosts. A post built from many blocks, a large pattern, or a complex page-builder layout can exceed that, so the save request is truncated and rejected. Raise max_input_vars to 3000 or more through php.ini or a .user.ini file, or ask your host. The companion limits post_max_size and max_execution_time cause the same length-dependent failure and are worth raising alongside it.
6. A stale nonce from caching, or a PHP notice in the response
These two pass the /wp-json/ test yet still break the save, which is what makes them confusing. A caching plugin, host cache, or CDN that caches a logged-in admin page can serve a stale nonce, and because the REST API treats a request with a missing or expired nonce as unauthenticated, the save fails. Exclude wp-admin and /wp-json/ from all caching and clear the cache. Separately, the REST reply must be only JSON, so if a plugin or theme prints output, or PHP emits a warning while display_errors is on, those characters contaminate the response and the editor cannot parse it. This is more common in 2026 as PHP 8.4 makes older plugins emit deprecation notices. Turn off display_errors in production and fix the plugin that is emitting them.
DIY vs hand it off
Many cases are a permalink re-save or a cache clear you can do in minutes. Others involve firewall rules, server software, or a hunt for a plugin contaminating responses. If the left column matches you can likely fix this. If the right column matches, get help.
Realistic on your own
- ✓The 60-second test points clearly at one cause
- ✓Re-saving permalinks or clearing the cache fixes it
- ✓You can temporarily disable a security plugin to confirm it
- ✓The failure is only on long posts and you can raise max_input_vars
- ✓You can match the WordPress Address and Site Address settings
- ✓You have a staging site and a recent backup to test on
Hand it off, save the time
- ✗A server-level firewall or mod_security is blocking REST before WordPress sees it
- ✗Site Health reports a loopback failure or cURL error 28 you cannot resolve
- ✗Your PHP, cURL, or OpenSSL is outdated and the host has to update
- ✗A PHP notice is contaminating responses and you cannot find the source plugin
- ✗The site is on a complex stack with a CDN, reverse proxy, and security suite
- ✗You are not comfortable editing .htaccess, php.ini, or firewall rules
How to diagnose it
Run these in order. The first two will identify most cases, and the later steps narrow the ones that pass the REST API test.
Visit your-site.com/wp-json/ in a new tab. Clean JSON means REST is reachable, go to the nonce, input-limit, and PHP-notice checks. A 403, login page, redirect, or HTML error means REST is blocked, go to the security, permalink, and SSL checks.
Open Tools then Site Health then Status. Look for a REST API error, the message that your site could not complete a loopback request, or a note that an active PHP session was detected, which interferes with REST and loopback requests. These point straight at the layer that is failing.
Temporarily deactivate the security plugin, or put Cloudflare or Sucuri in a paused or learning mode, then try to publish. If it works, that was the cause. Re-enable it and add a rule allowing REST API access for logged-in users rather than leaving protection off.
Go to Settings then Permalinks and click Save Changes without altering anything. This regenerates the rewrite rules and the .htaccess block, restoring the /wp-json/ routes if corrupted rewrite rules were the cause.
In Settings then General, confirm the WordPress Address and Site Address match and use https. Confirm the SSL certificate is valid and there is no mixed content. Then clear every cache and exclude wp-admin and /wp-json/ from caching so a fresh nonce loads.
Set WP_DEBUG and WP_DEBUG_LOG true with WP_DEBUG_DISPLAY false in wp-config.php, then reproduce the error and read wp-content/debug.log. A PHP warning written there at the moment of save is the output contaminating the JSON. For long-post-only failures, raise max_input_vars and retest.
How to fix it
Each branch stands alone. Pick the one your diagnosis pointed to.
If a security plugin or firewall is blocking REST
Confirm by disabling the plugin or pausing the firewall, then re-enable and configure rather than leaving it off. In the plugin or firewall log, find the blocked request to a /wp-json/ URL and add an allow rule for the REST API for logged-in users. In Wordfence and similar tools this is usually an allowlist entry or a setting that stops restricting the REST API. On Cloudflare or Sucuri, add a firewall exception for /wp-json/ so legitimate editor saves are not challenged.
If permalinks or .htaccess are broken
Go to Settings then Permalinks and click Save Changes to regenerate the rewrite rules. If .htaccess is not writable, restore the default WordPress block by hand.
# BEGIN WordPress
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteRule ^index\.php$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php [L]
</IfModule>
# END WordPressOn Nginx there is no .htaccess; confirm the server block passes /wp-json/ through to index.php with the standard try_files rule.
If URLs or SSL are mismatched
In Settings then General, set the WordPress Address and Site Address to the same value, both on https. Make sure the SSL certificate is valid and that the site does not add an extra redirect on the path to /wp-json/. Resolve any mixed-content warnings, since insecure resources on a secure page can break REST authentication. After changing URLs, clear caches and retest the editor.
If loopback or cURL 28 is the cause
Ask your host to confirm the server can make loopback requests to itself and is not blocking its own outbound HTTP. Update outdated server software: WordPress recommends PHP 7.4.13 or newer, cURL 7.74.0 or newer, and OpenSSL 1.1.1 or newer. If Site Health flags an active PHP session, find the plugin calling session_start() without closing it, since an open session interferes with REST and loopback requests.
If it only fails on long posts
Raise the PHP input ceiling. Add these to php.ini or a .user.ini in the site root, then confirm the new values under Tools then Site Health then Info then Server.
max_input_vars = 3000
post_max_size = 64M
max_execution_time = 120If you cannot edit those files, ask your host to raise max_input_vars above your largest post's variable count.
If caching or a PHP notice is contaminating the save
For the nonce case, exclude the admin and the REST API from caching so a fresh token always loads, then clear the cache. Most caching plugins and host caches let you exclude paths.
# Never cache these paths
/wp-admin/
/wp-json/For the contaminated-response case, turn off error display in production so notices cannot leak into the JSON, then find and update the plugin or theme generating them.
// wp-config.php: log errors, never print them in production
define( 'WP_DEBUG', true );
define( 'WP_DEBUG_LOG', true );
define( 'WP_DEBUG_DISPLAY', false );
@ini_set( 'display_errors', 0 );If the notices are PHP 8.4 deprecations from an outdated plugin, see our PHP 8.4 fix for the durable resolution.
Ran the test, tried the obvious fixes, and the editor still will not save?
Let us fix it in 2 hours →A real publishing-failed debug session
A composite from the editor repairs we run most weeks, with identifying details removed.
A content site suddenly could not publish or update any post, every save threw Updating failed, the response is not a valid JSON response. The team had not changed anything they were aware of. Short drafts and long articles both failed, and the site looked completely normal to visitors.
We ran the 60-second test. Visiting /wp-json/ returned a 403 Forbidden, so the REST API was being blocked, not contaminated. Site Health confirmed a REST API error. The site had a security plugin that had auto-updated two days earlier and shipped a new default rule restricting REST API access to stop content scraping. Pausing the plugin made saving work instantly, which confirmed it, and the firewall log showed the editor's own /wp-json/wp/v2/posts requests being blocked alongside the scrapers.
We re-enabled the security plugin and changed its REST API rule from block-all to allow authenticated, logged-in requests, so editors could save while anonymous scraping stayed blocked. We added an allowlist entry for the site's own address and confirmed the firewall log no longer flagged editor saves. Publishing and updating worked on both short and long posts, and the site kept its protection.
When to stop and hand it off
Four situations where DIY costs more than help. First, a server-level firewall or mod_security rule is blocking REST before WordPress even sees the request, which lives in host configuration you may not control. Second, Site Health reports a loopback failure or cURL error 28 that traces to DNS, server resources, or outdated PHP, cURL, or OpenSSL that the host has to update. Third, a PHP notice is contaminating responses and you cannot find which of dozens of plugins is printing it. Fourth, the site runs a complex stack of CDN, reverse proxy, and security suite where the block could be at any layer. We run the test, read the logs, isolate the exact layer, and fix it without leaving your security off. Flat $49 to $149, done in two hours when scope fits, money back if we cannot.
Get a quote in 60 secondsWordPress publishing-failed FAQ
What does Updating failed. The response is not a valid JSON response actually mean?
It means the block editor sent a save request to the WordPress REST API and got back something that was not the clean JSON it expected. The editor saves your post by calling an address under your-site.com/wp-json/, and it expects a structured JSON reply confirming the save. When it instead receives an HTML error page, a redirect, a 403 from a firewall, a login page, or JSON with stray text glued to the front, it cannot parse the response and shows this error. So the message is not really about JSON. It is the symptom of the REST API request being blocked, misrouted, or contaminated somewhere between the editor and the database. Updating failed appears when you save an existing post, and Publishing failed appears when you publish a new one, but both are the same underlying REST API problem.
What is the single fastest test to find the cause?
Open a new browser tab and visit your-site.com/wp-json/ directly. If the REST API is healthy, you will see a wall of structured JSON data describing your site. If instead you see a 404, a redirect somewhere else, a plain HTML error page, a login prompt, or a 403 Forbidden, the REST API is not reachable and that is your problem. This one test instantly splits the cause into two camps. If /wp-json/ returns good JSON, the editor problem is more likely a stale nonce from caching, a complex post exceeding a PHP input limit, or a PHP notice contaminating the specific save response. If /wp-json/ does not return good JSON, the REST API itself is blocked, usually by a security plugin or firewall, broken permalinks, or an SSL and URL mismatch.
A security plugin is the cause more often than anything else. How do I confirm it?
Security plugins like Wordfence, iThemes Security, and All In One WP Security, along with site firewalls like Cloudflare and Sucuri, frequently restrict or block REST API requests because a stream of requests to wp-json can look like abuse. This is the most common single cause in real audits. To confirm it, temporarily deactivate the security plugin, or put the firewall in a learning or paused mode, then try to publish again. If it works, the plugin or firewall was the cause. Do not leave it off. Instead, look in the plugin or firewall logs for the blocked wp-json requests and add a rule that allows REST API access for logged-in users, so you keep the protection without breaking the editor.
I reset permalinks and it fixed itself. Why?
The REST API lives at pretty URLs under /wp-json/, and those routes are created by the same rewrite rules that power your normal permalinks. If the rewrite rules or the .htaccess file get corrupted, often after a migration, a plugin change, or a manual edit, the /wp-json/ routes stop resolving and every REST request fails. Going to Settings then Permalinks and clicking Save Changes, even without changing anything, forces WordPress to regenerate the rewrite rules and rewrite the .htaccess block. That restores the REST API routing. It is one of the fastest fixes precisely because broken rewrite rules are a common and silent cause of the error.
What is a loopback request and why does cURL error 28 break publishing?
A loopback request is WordPress calling its own site over HTTP, which it uses for scheduled events, the built-in editors, and REST features. cURL error 28 means that request timed out, the connection was opened but no response came back in time. When loopback and REST requests time out, the editor never gets its save confirmation and you see the publishing error. WordPress Site Health reports this as your site could not complete a loopback request. The usual causes are a firewall blocking the site from calling itself, a DNS problem, limited server resources, or outdated server software. WordPress recommends PHP 7.4.13 or newer, cURL 7.74.0 or newer, and OpenSSL 1.1.1 or newer. If you are below those, ask your host to update. An active PHP session left open can also interfere with REST and loopback requests.
It only fails on long or complex posts. Why would length matter?
This points at a PHP input limit rather than a blocked REST API. The setting max_input_vars caps how many separate variables a single request can submit, and it defaults to 1000 on many hosts. A post built from many blocks, a large reusable pattern, or a complex page-builder layout can exceed that ceiling, so the save request is truncated and the REST API rejects it, producing the error only on big posts while short ones save fine. The fix is to raise max_input_vars to 3000 or higher in your PHP configuration, through php.ini or a .user.ini file, or by asking your host. The related limits post_max_size and max_execution_time can cause the same length-dependent failure and are worth raising together on stores and content-heavy sites.
Could it be caching, even though the page loads fine?
Yes, and it is an easy one to miss. The block editor authenticates its REST requests with a security token called a nonce, which is short-lived. If a caching plugin, a host page cache, or a CDN caches a logged-in admin page, it can serve you a stale nonce, and the WordPress REST API treats a request with a missing or expired nonce as an unauthenticated request, which then fails. The fix is to exclude the admin area and the REST API from caching: never cache wp-admin or /wp-json/, and clear the cache after configuration changes. If the error comes and goes, or appears only after the editor has been open for a while, a stale nonce from caching is a prime suspect.
A PHP warning is breaking my save even though the site looks fine. How?
The REST API must return clean JSON and nothing else. If a plugin or theme prints output, or PHP emits a warning or deprecation notice while display_errors is on, those characters get prepended to the JSON response. The editor then receives text followed by JSON, cannot parse it, and reports the response as invalid. This is increasingly common in 2026 because PHP 8.4 made older plugins emit deprecation notices. The fix is to turn display_errors off in production so notices cannot leak into responses, then identify and update the plugin or theme generating them. Enable WP_DEBUG_LOG to capture the messages to a file instead of the screen, and read the log to find the source.
My REST API works in the browser but the editor still fails. What now?
When /wp-json/ returns clean JSON but saving still fails, the problem is specific to the authenticated save request rather than the REST API as a whole. Work through the narrower causes in order. Clear every cache so the editor loads a fresh nonce. Raise max_input_vars if the failure is tied to long posts. Enable WP_DEBUG_LOG and watch the log while you reproduce the error, since a PHP notice contaminating that one response will show up there. Do a clean conflict test by switching to a default theme and disabling plugins one group at a time, because a single plugin hooking the save can break it while leaving the rest of the REST API healthy. One of these isolates it.
Sources and further reading
Every WordPress-specific claim on this page traces back to the WordPress REST API handbook, the WordPress Site Health and debugging documentation, or the WordPress permalink reference.
- WordPress Developer: REST API handbook
- WordPress Developer: REST API authentication, Application Passwords and nonces
- WordPress: Site Health screen (REST API and loopback checks)
- WordPress: Debugging in WordPress (WP_DEBUG and the debug log)
- WordPress: Permalink settings screen (rewrite rules and .htaccess)
- Kinsta: How to fix cURL error 28, connection timed out (loopback)