Skip to content
ThemesIonic — home
Troubleshooting

The REST API Encountered an Unexpected Result — Read the Status Code Site Health Shows

Site Health asked your own server for one REST API endpoint and got something other than HTTP 200 back. The code in the 'REST API Response' line names the cause, and a manual test without a nonce will mislead you.

5 min read intermediate

Site Health made a request from your server to its own REST API, and the answer was not HTTP 200 — the number in "REST API Response: (403) Forbidden" is the diagnosis. The notice is a "recommended" item under Performance, and it shares a cause with editor failures often enough that it is worth fixing rather than dismissing.

What the test does

WP_Site_Health::get_test_rest_availability() runs when you open Tools › Site Health. It:

  1. Builds the URL rest_url( 'wp/v2/types/post' ) with ?context=edit — the same kind of request the block editor makes.
  2. Sends it with wp_remote_get() from the server, passing your browser's cookies, a fresh X-WP-Nonce for wp_rest, Cache-Control: no-cache and a 10-second timeout. SSL verification is off for this local request.
  3. If you reached wp-admin through HTTP Basic authentication and PHP can see the credentials, it forwards them as an Authorization header.

It reports one of three results:

Site Health label What happened
The REST API encountered an error The request failed to complete: connection, DNS, timeout or TLS problem
The REST API encountered an unexpected result A server answered with a status other than 200
The REST API did not behave correctly 200, but the response lacked capabilities, so the context parameter was ignored

The first is a loopback problem, covered in loopback request failed. The third usually means a cache served the anonymous version of the endpoint. This article is about the second.

Read the status code

The notice prints REST API Endpoint: with the URL it tried and REST API Response: (code) message.

Response Usual cause
(401) Unauthorized HTTP authentication in front of WordPress that the request could not pass on
(403) Forbidden A firewall, security plugin or server rule blocked the request, or the login cookie was stripped (rest_cookie_invalid_nonce)
(404) Not Found /wp-json/ is not routed to WordPress
(500) Internal Server Error PHP failed while handling the REST request
(503) Service Unavailable Maintenance mode, an overloaded server or a rate limiter

401: the password stopped it

A 401 usually comes from HTTP authentication in front of WordPress, typically on a password-protected staging site. WordPress forwards Basic credentials only when PHP receives them in PHP_AUTH_USER and PHP_AUTH_PW. When the server checks the password but does not pass the Authorization header on to PHP (Apache with PHP-FPM does not by default), or the password uses another scheme, the loopback request arrives without it and the web server answers 401 before WordPress runs. Allow the server's own IP through the password, or accept the notice on staging. A security plugin that restricts REST to logged-in users can also answer 401; its log will show the rule.

403: something blocked it

A request from the server to itself comes from the server's IP, often with no browser user agent. Security plugins that disable REST for unknown clients, WAF rules and "block requests from data-center IPs" settings hit exactly that profile. Check the plugin's or firewall's log for the request time and allowlist the server's own address rather than disabling REST.

If the JSON body says rest_cookie_invalid_nonce ("Cookie check failed"), the cookies never arrived. The nonce Site Health sends belongs to your user; without the login cookie WordPress checks it as a logged-out visitor, and it fails. Caching layers that strip the Cookie header on every path except /wp-admin/ cause this, because rest_url() is built from the Site Address and the test goes to the public /wp-json/. Exclude /wp-json/ from cookie stripping.

404: the route is not reachable

/wp-json/ depends on rewrite rules. Save Settings › Permalinks once, check the server config with permalinks not working, and if WordPress answers with a JSON rest_no_route error, follow rest_no_route.

500: read the log

Find the PHP fatal at that timestamp in the debug log. A plugin registering REST routes or filtering responses is the usual culprit.

Test it the right way

Here is the trap. The obvious manual check — open /wp-json/wp/v2/types/post?context=edit in a logged-in tab, or curl it from the server — returns 401 on a healthy site. Without a nonce, rest_cookie_check_errors() treats the request as anonymous, and the edit context requires edit_posts. People "confirm" a fault that does not exist and start changing things.

Test the two halves separately. Inside WordPress, bypassing HTTP:

wp --user=admin eval '$r = rest_do_request( new WP_REST_Request( "GET", "/wp/v2/types/post" ) ); echo $r->get_status(), PHP_EOL;'

The network path from the server, using the public index route, which needs no login:

curl -sI https://example.com/wp-json/

If WP-CLI prints 200 but curl from the server gets 403 or hangs, the fault is in the network path: firewall, proxy or DNS. If both pass, open the block editor and run await wp.apiFetch( { path: '/wp/v2/types/post?context=edit' } ) in the console — it sends the nonce, just like Site Health.

Common mistakes

  • Treating a nonce-less 401 as proof that REST is broken.
  • Disabling the REST API to silence a security scan, then wondering why the editor shows Publishing failed.
  • Allowlisting your own IP when the blocked request comes from the server's.

Verify

  • Tools › Site Health no longer lists the item; reload the page to rerun the test.
  • wp eval with rest_do_request() returns 200.
  • curl -sI https://example.com/wp-json/ from the server returns 200.
  • The block editor saves a draft without errors.

Frequently asked

Sometimes. The test runs from the server to itself, so a firewall that blocks the server's own IP or a staging password can fail it while your browser works. If the editor saves, the REST API works for you; fix the loopback path anyway if scheduled tasks, updates or other server-side features also report problems.
'Encountered an error' means the request never completed: a connection failure, timeout or certificate problem, reported with a WordPress error code. 'Unexpected result' means a server answered, but not with HTTP 200, and Site Health shows the status code and message it got.
Without a REST nonce, WordPress treats cookie-authenticated requests as anonymous, and the edit context requires the edit_posts capability. A 401 rest_forbidden_context there is normal. Test from the block editor's console with wp.apiFetch, which sends the nonce.

Related guides