Skip to content

HTTP Headers Checker

HTTP response headers are the metadata a server sends with every page: how long the response may be cached, what content type it is, which CDN handled it, and what security policies apply. This tool issues a real GET request from our server and shows you every header the origin actually returned.

Why a GET and not a HEAD

Most header tools send HEAD because it is cheaper. That is exactly why their results are sometimes wrong. WAFs, bot filters, and a good number of application frameworks handle HEAD differently from GET — returning 405, omitting Content-Length, skipping compression negotiation, or short-circuiting before the middleware that sets security headers ever runs. This tool sends a normal GET, follows redirects, and reports the headers on the final response, which is what a browser would see.

Headers are shown for the final response after redirects. If you need the headers on an intermediate hop — a 301's cache directives, for instance — trace it with the redirect checker, which shows every hop separately.

The caching headers, in the order they are applied

Cache-Control decides everything; the older Expires header only matters when Cache-Control is absent. The directives that actually change behaviour:

DirectiveEffect
max-age=NFresh for N seconds in any cache, including the browser
s-maxage=NOverrides max-age for shared caches (CDN, proxy) only
no-cacheMay be stored, but must be revalidated before reuse — not the same as no-store
no-storeMust not be written to disk or memory at all; the correct header for authenticated pages
privateBrowser may cache, shared caches must not
immutableSkip revalidation entirely for the lifetime — only safe on fingerprinted asset URLs
stale-while-revalidate=NServe stale for up to N seconds while refreshing in the background
Read the full explanation of http headers checker results ↓

The single most common production mistake is Cache-Control: no-cache on an authenticated page, in the belief that it prevents storage. It does not — it permits storage and requires revalidation. For anything containing personal data the correct value is no-store, usually paired with private.

Vary is the other header worth reading carefully. Vary: Accept-Encoding is normal and necessary. Vary: User-Agent fragments your CDN cache into thousands of near-identical entries and effectively disables it; Vary: Cookie on a page that sets an analytics cookie does the same thing. If your CDN hit rate is inexplicably bad, this is the first header to look at.

Reading the CDN and origin headers

  • cf-cache-status, x-cache, x-served-by — whether the edge served a HIT, a MISS, or bypassed the cache entirely. Repeated MISS on a static asset usually means the origin is sending no-store or a Set-Cookie that suppresses caching.
  • age — how many seconds the shared cache has held this response. A large age on a page you just deployed means the purge did not happen.
  • server, via, x-powered-by — the software in the path. x-powered-by leaks your framework and version for no benefit; remove it.
  • alt-svc — advertises HTTP/3 availability. Its absence is why a site can be HTTP/3-capable and still never negotiate it.
  • etag and last-modified — the validators that make conditional requests work. A response with neither cannot be revalidated cheaply, so every expiry costs a full transfer.

Common problems and what causes them

What you seeLikely causeFix
Deployed change not visibleLong max-age plus no purge, or high age at the edgePurge the CDN; fingerprint asset filenames instead of relying on short TTLs
No content-encodingCompression off, or stripped by a proxyEnable gzip/brotli at the edge and confirm Vary: Accept-Encoding is set
content-type: text/plain on HTMLWrong MIME mappingFix the server MIME types; with nosniff set the page will not render at all
set-cookie on static assetsA framework session cookie applied globallyScope the session middleware to dynamic routes — cookies suppress shared caching
CDN never caches anythingOver-broad Vary, or Cache-Control: privateNarrow Vary to Accept-Encoding; use s-maxage for shared caches
403 or 503 from this tool but not your browserA WAF or bot filter blocking a non-browser User-AgentExpected — confirm with curl using the same UA
Charset matters. Content-Type: text/html with no ; charset=utf-8 leaves the browser to guess, which is both a rendering bug and a historical XSS vector. Always send it explicitly.

The same request with curl

curl -sL -o /dev/null -D - https://example.com

HTTP/2 200
content-type: text/html
cache-control: max-age=3077
age: 3077
server: cloudflare
Real GET, follow redirects, print only the headers of every response
for i in 1 2; do
  curl -sI https://example.com | grep -Ei '^(cf-cache-status|age|x-cache):'
done
Check whether the edge is serving a cached copy, twice in a row
curl -sI -H 'Accept-Encoding: br, gzip' https://example.com \
  | grep -Ei '^(content-encoding|vary):'
Confirm compression is actually negotiated

For the security-relevant subset of these headers — HSTS, CSP, frame protection, nosniff, Referrer-Policy, Permissions-Policy — the security headers checker grades them against a fixed published rubric and tells you exactly what to add.

Frequently asked questions

Why do the headers here differ from the ones in my browser dev tools?
Usually because your browser served the response from its own cache, or a service worker intercepted it. Dev tools also merge in headers from earlier hops. Reload with cache disabled and compare against the final URL shown here. A geographic difference is also possible — this request comes from our server, which may hit a different CDN edge than you do.
What is the difference between no-cache and no-store?
no-cache allows the response to be stored but requires revalidation with the origin before it is reused. no-store forbids writing it anywhere at all. For pages containing personal or authenticated data you want no-store, private. Using no-cache there is a very common and genuinely risky mistake.
Should I remove the Server and X-Powered-By headers?
Remove X-Powered-By — it advertises framework and version and buys you nothing. Server is harder to remove cleanly and is low value to an attacker who can fingerprint you other ways, so it is not worth breaking a config over. Neither is a substitute for patching; obscurity is not the control.
Why does my CDN show a MISS on every request?
Most often a Set-Cookie on the response, Cache-Control: private or no-store, or an over-broad Vary header. Shared caches refuse to store responses that set cookies. Scope session middleware to the routes that need it, and narrow Vary to Accept-Encoding.
Does this tool follow redirects?
Yes — it follows them and reports the headers of the final response, with the final URL shown above the table. To see the status codes and Location headers of each individual hop, use the redirect checker instead.
Why did I get a 403 here when the site loads fine for me?
A WAF or bot filter is rejecting the request based on User-Agent, IP reputation, or the absence of browser-like headers. That is the filter working as configured, not a fault in the site. Reproduce it with curl -A using the same User-Agent to confirm, and allowlist the check if you need it to run on a schedule.
Are request headers shown too?
No — this shows the response headers the server sends back. To see the request headers your own browser sends, including your public IP and User-Agent as the server sees them, use What's My IP.