
If your WordPress REST API is returning old data, a cache is almost always answering for WordPress. It is usually a CDN rule, a caching plugin that stores /wp-json/ responses, or a host-level cache. Test with a fresh URL, read the response headers to find which layer replied, then exclude /wp-json/ from that cache and purge it.
You save a change in the dashboard, call /wp-json/wp/v2/posts, and the JSON still shows yesterday’s title. Or your app saves a setting, reports success, and the value is gone after a reload. Nothing is wrong with the database. Something between WordPress and the person (or script) asking is handing out a stored copy.
This guide shows how to find which layer is doing it in a few minutes, using only a browser and one command. Then it covers the exact exclusion to set for Cloudflare, LiteSpeed Cache and WP Rocket, and the one case that looks like stale data but is really a login problem.
WordPress REST API returning old data: how to confirm it is a cache
Start with the fastest test: ask for the same data on a URL no cache has seen before. Add any made-up parameter to the end, for example /wp-json/wp/v2/posts?per_page=1&test=4821, and change the number each time. WordPress ignores the extra parameter, but many caches treat a different query string as a different page.
- Open your normal URL, such as
https://example.com/wp-json/wp/v2/posts?per_page=1, and note the title andmodifieddate it returns. - Open the same URL with
&test=4821added. If this one shows the fresh data, a cache is serving the plain URL. - Open the plain URL in a private window. If it is still old, the copy is not in your browser. It is further upstream.
- Run
curl -I "https://example.com/wp-json/wp/v2/posts?per_page=1"and read the headers (next section).
If the fresh URL also shows old data, the problem is not caching. Check that you saved the right post, that it is published, and read the login section further down.
Read the headers to find which layer answered
The response headers tell you who replied. Two matter most. cf-cache-status is added by Cloudflare. cache-control is the instruction that tells every cache whether it may keep the response.
Cloudflare’s documentation defines the cache status values this way: HIT means the response came from Cloudflare’s cache, MISS means it was cacheable but not stored yet, DYNAMIC means Cloudflare decided at request time that the item is not eligible for caching, and BYPASS means the origin’s response could not be cached, for example because of no-store or a Set-Cookie header.
| What you see | What it means | Where to fix it |
|---|---|---|
cf-cache-status: HIT on a /wp-json/ URL |
Cloudflare is serving a stored copy | Cloudflare Cache Rule with Bypass cache |
cf-cache-status: DYNAMIC or BYPASS, data still old |
Cloudflare is not the cause | Caching plugin or host cache |
No cf-cache-status header at all |
The site is not proxied by Cloudflare | Caching plugin, host cache, or the client app |
cache-control: public, max-age=... with a number above 0 |
Something is telling caches to keep the response | Plugin or host setting that adds the header |
Fresh in curl, old in the browser only |
Browser or app-level caching | Hard reload, or fix the client code |
Fix 1: Cloudflare is caching /wp-json/
By default Cloudflare caches by file extension, not by content type, and its documentation states that it does not cache HTML or JSON by default. So if Cloudflare is serving old JSON, something opted the URL in. The usual causes are a “cache everything” style rule, a page rule an old setup left behind, or a plugin or host that sends Cache-Control: public with a max-age above 0. Cloudflare’s rules say a response is cached when it has that header, and not cached when the header is private, no-store, no-cache or max-age=0.
The clean fix is a rule that tells Cloudflare never to cache the API path. If you are not sure what your plan covers, our Cloudflare free vs Pro guide lists what the free plan includes.
- In the Cloudflare dashboard, open the zone for your domain and go to the Cache Rules page, then click Create rule.
- Give it a name such as
Bypass WordPress REST API. - Under When incoming requests match, choose Custom filter expression, click Edit expression, and paste:
starts_with(http.request.uri.path, "/wp-json/") - Under Then, in Cache eligibility, choose Bypass cache.
- Under Place at, put the rule above any broader caching rules, then click Deploy.
If your site uses plain permalinks, the API lives at ?rest_route=/ instead of /wp-json/, according to the WordPress REST API handbook. In that case, match the query string as well, for example by adding or http.request.uri.query contains "rest_route=" to the expression.
Purge the old copy afterwards
A new rule stops future caching but does not delete what is already stored. In the dashboard, open the Configuration page in the Cache section, choose Custom Purge, set Purge by to URL, enter the API URL, and click Purge. The path part of the URL is case-sensitive. Cloudflare notes that dashboard single-file purge does not clear resources that use custom cache keys or certain special headers, so use the API for those.
Fix 2: A caching plugin is storing REST responses
This is the most common cause on sites that are not behind Cloudflare, and it is easy to miss because the plugin’s page cache setting looks unrelated. Some plugins treat the REST API like any other URL.
LiteSpeed Cache
LiteSpeed’s documentation lists a Cache REST API option on the Cache tab, and it is ON by default: “This option allows requests made through WordPress REST API calls to be cached.” Turn it off if your editor, plugin settings screens or an app depend on live data. You can also add /wp-json/ to Do Not Cache URIs on the Excludes tab, one URI per line. Purge all caches afterwards.
WP Rocket
Go to the Advanced Rules tab and find Never cache URL(s). Add /wp-json/(.*) on its own line. WP Rocket’s documentation says simple regex is supported and that you can enter either the full URL or only the part after the domain. If you run it on a managed host such as WP Engine or Kinsta, the same documentation says you must ask the host’s support to apply the exclusion.
Other cache plugins
Every major plugin has a “never cache these URLs” box. Look for it and add /wp-json/. One developer report found W3 Total Cache returning empty REST responses even though it was set not to cache the API, and the problem disappeared only when the plugin was disabled. If you hit that situation, disable the plugin briefly to confirm, then reconfigure or replace it. Keep page caching for normal pages and exclude only the API path.
Fix 3: The browser or your own app is reusing a copy
If curl shows fresh data but a browser tab or dashboard shows old data, the stale copy is local. WordPress core ticket #39861 documents reports of browsers reusing REST responses, including a case where pressing Back loaded REST data from the browser’s disk cache, and a case where a host added a bogus Last-Modified header. That ticket is still open, so core does not solve it for you.
- Hard reload with Ctrl + Shift + R, or test in a private window.
- If you wrote the front-end code, check for your own caching layers, such as a stored fetch result, a service worker or a saved copy in a variable.
- If you run a custom plugin, check whether it saves API results for later reuse and whether the saved copy ever expires.
- Ask your host whether it adds its own page cache or CDN in front of PHP, since that can add
Last-ModifiedorCache-Controlheaders you did not set.
When it looks like stale data but is really a login problem
Sometimes the data is not old at all. It is the public version. The WordPress handbook says cookie authentication needs a nonce, sent in the X-WP-Nonce header or the _wpnonce parameter, and that without one the request is treated as unauthenticated even if you are logged in. An unauthenticated call to /wp/v2/posts returns published posts only, so a draft you just saved will not appear and the response looks outdated.
For scripts and outside apps, the handbook describes application passwords (added in WordPress 5.6), used with HTTP Basic Auth over HTTPS. Create one on the user’s edit screen in the dashboard, then test with a call such as curl --user "USERNAME:APP-PASSWORD" "https://example.com/wp-json/wp/v2/posts?context=edit&status=draft". If drafts now show up, it was authentication, not caching.
Do not turn on “cache everything” for a domain that also serves the WordPress admin or REST API. If you need aggressive caching for public pages, scope the rule to the front end and keep /wp-admin/ and /wp-json/ out of it. The same thinking applies to the cache layers described in our guide on Elementor changes not showing.
A short checklist to confirm the fix
- Edit a post and save it.
- Request
/wp-json/wp/v2/posts/POST_ID?_fields=id,modifiedon the plain URL. The WordPress handbook documents_fieldsfor trimming a response to the properties you list. - Confirm the
modifiedvalue matches the time you saved. - Run
curl -Iagain and confirm the header no longer says HIT. - Try again from a different network or your phone to rule out a local copy.
If the API still lies after all of that, the next suspect is the connection itself. A redirect loop or certificate problem can look like missing data, so see our posts on Cloudflare too many redirects and AutoSSL not working with Cloudflare.
Stale REST API data means a cache is answering. Compare a normal URL with one carrying a throwaway parameter, read cf-cache-status and cache-control, then bypass /wp-json/ in whichever layer replied: a Cloudflare Cache Rule, your caching plugin’s exclusion list, or the host’s cache. Purge once, then recheck.
Frequently asked questions
Does Cloudflare cache the WordPress REST API?
Not by default. Cloudflare’s documentation says it does not cache HTML or JSON by default. It will cache the API if you add a rule that makes it eligible, or if your site sends a Cache-Control: public header with a max-age above 0.
Why does the REST API show old data only when I am logged out?
Two things can cause it. A shared cache may hand an older stored copy to anonymous requests. Separately, unauthenticated requests only return public data, so recent drafts and private content are missing from the response. The headers test above tells you which one you have.
Should I disable caching for all of wp-json?
For most small sites, yes. The API is mostly used by the editor, plugins and your own scripts, which all need current data. If you serve a heavy public API, cache only specific read-only routes and set a short lifetime.
How do I bypass the cache for a single request?
Add a throwaway parameter such as &test=123 to the URL. It is a diagnostic trick, not a fix, because the next normal request can still be served the old copy. The real fix is an exclusion for the path.
Why does the dashboard say the setting saved but the old value comes back?
The save request reaches WordPress, but the page that reads the value gets a cached response. A plugin vendor’s knowledge base describes exactly this when a CDN caches REST responses, and recommends bypassing the cache for everything under /wp-json/.
Sources: Cloudflare default cache behavior; Cloudflare cache responses; Cloudflare: create a cache rule in the dashboard; Cloudflare: purge by single file; WordPress REST API authentication; WordPress REST API global parameters; WordPress REST API key concepts; LiteSpeed Cache settings; WP Rocket: exclude URLs from cache; WordPress Trac ticket 39861; devowl.io: CDN REST API caching. Checked October 2026.


