Glossary

Cloudflare Headers Explained: cf-cache-status, cf-ray and More

November 13, 2026

You have put your WordPress site behind Cloudflare and want to know whether the CDN is actually doing anything. Suddenly the responses from your server contain lines like cf-cache-status: DYNAMIC or cf-ray: 8f2a1c3d4e5f6a7b-FRA. These headers are something like the package insert that comes with Cloudflare: they show you whether a file came from the cache, which data center answered and whether a security rule stepped in. Here you will learn what the individual values mean and what you can conclude from them for your site.

What are Cloudflare headers and how do you read them?

Cloudflare sits as a proxy between your visitors and your server. Every request travels through the Cloudflare network, and headers are added along the way. Some of them are visible to the browser of your visitors, others only reach your server. How such a network works in general is covered in our article What is a CDN?.

The quickest way to see the headers is the developer tools of your browser. Open the “Network” tab, reload the page and click the first row. The terminal is even quicker:

curl -sI https://example.com/

The response then looks roughly like this:

HTTP/2 200
content-type: text/html; charset=UTF-8
cache-control: max-age=0
cf-cache-status: DYNAMIC
server: cloudflare
cf-ray: 8f2a1c3d4e5f6a7b-FRA

If there are no lines starting with cf- at all, the request does not go through Cloudflare. In most cases the DNS record is then set to “DNS only” (grey cloud) instead of “Proxied” (orange cloud).

What does cf-cache-status mean? All values at a glance

The cf-cache-status header is the most important one of all. It tells you how Cloudflare handled this exact response. The official documentation lists nine values:

  • HIT: The file was in the cache and came straight from Cloudflare. Your server never noticed the request. This is the value you want to see.
  • MISS: The file may be cached but was not yet stored in this data center. Cloudflare fetched it from your server. On the next request you should see HIT.
  • EXPIRED: The file was in the cache but had expired. Cloudflare fetched a fresh copy from your server.
  • REVALIDATED: The cached copy had expired. Cloudflare asked your server whether anything had changed, and the answer was no. So the file came from the cache, but the visitor had to wait for that check.
  • UPDATING: The copy had expired and was delivered right away anyway, while Cloudflare refreshes it in the background. This happens when your server sends stale-while-revalidate.
  • STALE: Cloudflare delivered an expired copy because your server could not be reached. A warning sign you should take seriously.
  • BYPASS: The file would have been a candidate for the cache, but the response from your server prevented it, for example with a cookie.
  • DYNAMIC: Cloudflare did not even look in the cache, because under the current rules this content is not cached.
  • NONE/UNKNOWN: Cloudflare generated the response itself, for example for a redirect to HTTPS, a block by the firewall or a Worker.

Good to know: since May 26, 2026, Cloudflare separates two of these values more cleanly. Before that, some responses that could not be cached returned MISS on every single request. Since the change they always return BYPASS. MISS now only means: cacheable, but not in stock right now. Many older guides on the web still describe the previous behavior.

Why does your WordPress site show cf-cache-status: DYNAMIC?

This is the most common question on the topic, and the answer is reassuring: for a regular WordPress page, DYNAMIC is the normal case. By default Cloudflare caches neither HTML nor JSON. The decision is based on the file extension, not on the content. Images, CSS, JavaScript, fonts and PDFs end up in the cache, the HTML of your pages does not.

There is a good reason for that. WordPress builds pages dynamically, and Cloudflare cannot know whether the HTML currently contains a shopping cart, an admin bar or a personal greeting. If it cached such pages without being asked, one visitor might end up seeing the cart of another.

Your images and scripts, on the other hand, should report HIT. So do not just check the home page, check a CSS file or an image as well.

If you want Cloudflare to deliver the HTML too, you have two options:

  • Cache Rule: You create a rule in the dashboard that marks HTML as “Eligible for cache”. Then it is up to you to make sure that logged in users, the admin area, cart and checkout stay excluded.
  • APO for WordPress: Automatic Platform Optimization handles these exceptions for you and clears the cache as soon as you change a post. More on that further down.

If images or CSS show DYNAMIC as well, the cause is usually a rule with “Bypass cache” or Development Mode. The latter switches the cache off completely for three hours and is easily forgotten.

cf-cache-status BYPASS or always MISS: what is the cause?

With BYPASS, Cloudflare wanted to cache, but the response from your server did not allow it. The troubleshooting guide names these causes above all:

  • The response contains a Set-Cookie header. On WordPress this is the classic: a shop, a multilingual plugin or a tracking plugin sets a cookie on every request, and nothing gets cached anymore.
  • Your server sends Cache-Control: no-store or private.
  • The response contains Vary: *.
  • The file is larger than the limit of your plan (512 MB on Free, Pro and Business).

If every request stays at MISS, the file is cacheable, but Cloudflare never finds it again. Changing parameters in the address are often behind this, such as utm_source or a session ID, because every variant counts as a separate file. On sites with few visitors, rarely requested files also drop out of the cache sooner. And every data center has its own cache: if two requests land at different locations, you will see MISS twice.

A good helper is the Age header. It states in seconds how long the copy has been in the cache. If you request a file twice in a row and see HIT with a growing Age, everything works.

What does the cf-ray header tell you?

Every request that passes through Cloudflare gets its own identifier, the Ray ID. It appears in the cf-ray header and has two parts: a string of characters and a three letter code. The code names the data center that answered. FRA stands for Frankfurt, AMS for Amsterdam, CDG for Paris.

In daily work the Ray ID helps you in three places. When Cloudflare shows a visitor an error page, the Ray ID is printed at the bottom, and with it you can find that exact request again in the logs or the Security Events. Second, the code shows you whether your visitors really end up at a nearby location. And third, it explains many a puzzling MISS when two requests arrived at different data centers.

By the way, Cloudflare sends the same Ray ID to your server too. If you write it to your log there, you can follow a request all the way from the browser to PHP.

cf-apo-via and cf-edge-cache: is APO for WordPress working?

If you use Automatic Platform Optimization, two more headers show up. According to Cloudflare, you can recognize a working setup by three lines:

  • cf-cache-status: HIT shows that the HTML came from the cache.
  • cf-apo-via: tcache shows that APO served the request.
  • cf-edge-cache: cache,platform=wordpress confirms that the Cloudflare plugin is installed and active in WordPress.

If cf-edge-cache is missing, the plugin is usually deactivated or not connected to your account. If cf-apo-via shows a value other than tcache, APO did not deliver the page from the cache, for example because you are logged in. So always test in a private window.

What does cf-mitigated: challenge mean?

This header only appears when Cloudflare delivers a challenge page instead of your page, the familiar notice that the visitor is being verified for a moment. challenge is the only valid value.

For people this is usually just a short delay. For anything that accesses your site automatically it is a real obstacle. The challenge page is always HTML, even when JSON was requested. If your own JavaScript calls the REST API and suddenly gets a challenge page back, it runs into an error that you could hardly make sense of without this header. The same goes for monitoring services, payment providers with webhooks and analysis tools. If you see cf-mitigated: challenge on requests that should get through, take a look at the Security Events in the dashboard. They tell you which rule was triggered.

Which Cloudflare headers reach your server?

Not all headers are meant for the browser. Cloudflare attaches some of them to the request before it reaches your server. The full list is long, but for WordPress these matter most:

  • CF-Connecting-IP: the real IP address of the visitor. Without this header your server only sees addresses that belong to Cloudflare.
  • X-Forwarded-For: the chain of all proxies the request passed through. Cloudflare recommends reading CF-Connecting-IP for the visitor address instead.
  • X-Forwarded-Proto and CF-Visitor: show whether the visitor arrived over HTTP or HTTPS.
  • CF-IPCountry: the country of the visitor as a two letter code. XX means unknown, T1 stands for the Tor network.
  • CDN-Loop: prevents a request from running through Cloudflare in circles forever.

The real IP address is more than a detail. Security plugins block attackers by IP address, WordPress stores it with comments, and your host evaluates it in the logs. If only the address of Cloudflare arrives there, your plugin may end up locking out all visitors at once. Most hosts and security plugins can be configured for this. One thing matters here: only trust the header if the request really comes from an address inside the Cloudflare network. Otherwise anyone can write any IP address they like into the header.

A well known source of errors is connected to this as well. With the SSL setting “Flexible”, Cloudflare talks to your server without encryption. WordPress then believes the visitor came over HTTP, redirects to HTTPS, and an endless loop begins. The clean solution is a certificate on the server and the setting “Full (strict)”.

How do you control what Cloudflare caches with Cache-Control?

How long Cloudflare keeps a file is decided first and foremost by your server. There are three headers for this, which Cloudflare evaluates in this order:

  1. Cloudflare-CDN-Cache-Control applies to Cloudflare only and is not passed on to the browser.
  2. CDN-Cache-Control applies to all CDNs along the way.
  3. Cache-Control applies to browsers and, if the other two are missing, to Cloudflare as well.

This lets you control browser and CDN separately. An example: the browser should keep a file for only one hour, Cloudflare for a whole day.

Cache-Control: max-age=3600
Cloudflare-CDN-Cache-Control: max-age=86400

If your server sends no instructions at all, the default values apply. Responses with status 200 and 301 stay in the cache for 120 minutes, redirects with 302 and 303 for only 20 minutes, errors with 404 and 410 for three minutes. That last point surprises many people: if you upload a missing image afterwards, Cloudflare may keep delivering the old error for a few more minutes.

Which other headers does Cloudflare set?

Besides the well known lines, you may notice a few others:

  • server: cloudflare replaces the name of your web server. Whether Apache, nginx or LiteSpeed runs behind it can no longer be seen from the outside.
  • report-to and nel belong to Network Error Logging. Through them the browser reports connection errors to Cloudflare. The feature can be switched off in the dashboard, which is interesting for some sites with regard to privacy.
  • alt-svc tells the browser that the site can also be reached over HTTP/3. Cloudflare removes an alt-svc header set by your own server.

A few headers from your server never reach the visitor, by the way. These include Cache-Tag and the X-Accel headers of nginx. So do not be surprised if you look for them in the browser in vain.

How do you check Cloudflare headers with InspectWP?

InspectWP reads the headers of your site when it creates a report and shows you cf-cache-status and cf-ray, among others, in the HTTP headers section. So without a terminal you can see whether your site runs through Cloudflare and how the home page was delivered. On top of that come the values related to caching: compression, the HTTP version and the security headers.

This is most useful when you look after several sites. With automatic reports you notice when a page suddenly no longer comes from the cache or when Cloudflare is no longer in front of the site after a migration. If a Cloudflare challenge page blocks the crawl, InspectWP points that out. Our help article What to do when the crawl fails explains how to adjust the bot protection for it. A free report shows you in a short time which headers your site is sending right now.

Check your WordPress site now

InspectWP analyzes your WordPress site for security issues, SEO problems, GDPR compliance, and performance — for free.

Analyze your site free