A server response being inspected, with some security headers present and others visibly absent

The short answer: when a header checker says a header you definitely set is missing, the header is almost never the problem — the response being checked is. A checker reads one exact response, and that response may be a redirect instead of the page, a cached copy from before your change, an error page your config does not cover, or a route handled by a different block of configuration than the one you edited. Work through the six causes below in order, and you will find which response you are actually looking at — then the fix is usually a one-line change in the right place.

Diagnose it first: which response did the checker actually read?

Before changing any configuration, pin down what was checked. This table maps the checker's finding to the most likely cause:

What the checker shows Most likely cause Check this first
All headers missing on a URL that redirects You checked the redirect response, not the final page Check the final URL after the redirect
Headers missing on one host variant only (www vs apex, http vs https) Headers set on one virtual host / server block, not the other Check every host variant of the same page
Headers missing everywhere, right after you deployed them CDN or proxy cache still serving the pre-change response Purge the CDN cache for the URL, then re-check
Headers present on the homepage, missing on sub-pages Server-block inheritance wiped out by a location block (nginx) Compare a sub-page response against the homepage response
Headers present on normal pages, missing on error pages Headers only added for success responses, not errors Check a 404 page on the same site
One header missing while the rest are present That header set via meta tag, or set at a layer that does not apply to this route Confirm the header comes from the server response, not the HTML

1. You checked the redirect, not the page

This is the most common cause, and the easiest to miss. A header checker that does not follow redirects — and several do not, deliberately, so you see the exact response for the URL you entered — reports on the redirect response itself. Redirect responses are typically bare: a status code, a Location header, and little else. Your security headers live on the final page, one or two hops away, and the checker never saw them. The same trap applies to host variants: headers set on the www host do not automatically appear on the apex host, and headers set for HTTPS do not appear on the plain-HTTP response that only exists to redirect. The fix is procedural, not configurational: trace the redirect chain first to find the final URL, then check the headers on that final URL. If the final page has the headers, nothing was ever broken — you were reading the wrong response.

2. A CDN or proxy is serving yesterday's response

If you deployed headers and the checker still shows the old response minutes or hours later, suspect caching before suspecting your configuration. CDNs and reverse proxies cache full responses, headers included, and a cached response from before your change carries the old header set no matter what the origin now sends. This is also why the problem can look inconsistent: one edge location has the new response, another still serves the old one, and the checker hits whichever edge answers. Purge the cache for the affected URLs (a full purge if your provider makes single-URL purges awkward), wait for the purge to propagate, and re-check. If you cannot purge on demand, check with a cache-busting query string — but remember that a query string can also change which cached copy you get, so treat it as a diagnostic hint, not proof. The real confirmation is a fresh response from the origin with the headers present. And while you are there, check whether the CDN itself is stripping headers: some providers remove or override origin headers unless their own edge rules are configured to pass them through, which is a second, quieter version of the same problem.

3. One nginx location block wiped out the rest (inheritance)

On nginx, this one bites experienced administrators too. The rule, stated in the nginx headers module documentation, is that add_header directives are inherited from the previous configuration level only if the current level defines no add_header directives of its own. The moment a location block sets even one header, it silently drops every header set at the server level for requests handled by that block. So the homepage (served by the server block) shows all six headers, while every page under a location block that sets, say, a Cache-Control header shows only that one. The symptom — headers present on some routes, missing on others — is the giveaway. The fix is to repeat the full header set inside the location block, move the shared headers into a snippet included by every block, or use add_header_inherit on nginx 1.29.3 and later to merge levels explicitly. Apache users hit the cousin of this problem with .htaccess overrides and per-directory configuration: always compare the response of a working route against a broken route before assuming the whole server is misconfigured, because a route-level difference is the evidence that points at the right block.

4. You set it in a meta tag, and meta tags do not count for these headers

For three of the most-checked headers — Strict-Transport-Security, X-Frame-Options and X-Content-Type-Options — an HTML meta tag is not an alternative delivery mechanism; browsers simply ignore them when they arrive that way. Only Content-Security-Policy and Referrer-Policy have meta-tag equivalents, and even there the header form is preferable (a meta-based CSP cannot use frame-ancestors or report violations reliably). So a page can look perfectly configured in its HTML source while every header checker correctly reports the security headers missing — because the checker reads the HTTP response, and the response never contained them. If your "fix" was editing a template, move it: set headers in the web server configuration, application middleware, or CDN edge rules, where they attach to the response itself. MDN's HTTP headers reference is the reliable place to confirm, header by header, which delivery mechanisms each one supports. The OWASP Secure Headers Project is the matching checklist for which headers an audit will look for in the first place.

5. Headers that only apply to success responses

nginx's add_header, by default, adds headers only for a specific set of response codes — 200, 201, 204, 206, 301, 302, 303, 304, 307 and 308. A 404 page, a 500 page, or any error response outside that set gets none of them unless you add the always parameter. That produces a confusing pattern: the site looks fully configured on every normal page, while a checker pointed at a mistyped URL or an error page reports everything missing. It matters more than it looks, because error pages are real pages that browsers render and attackers probe, and because a checker run against a URL that happens to 404 will condemn a configuration that is actually fine everywhere else. The fix is the always flag on each header directive, and the diagnostic is a single check of a 404 page on the same site. If the headers appear there too, this cause is ruled out for good.

6. The route is handled somewhere else entirely

Modern sites are rarely one server. Static assets may come from object storage, API routes from a separate service, and the marketing pages from the main app — each with its own header configuration, and often with a CDN layer in front deciding which origin serves what. A header set in the application's middleware therefore appears on app pages and nowhere else: not on images, not on the API, not on the docs subdomain. The checker is not wrong; it is reporting a different origin's response. Map the route first: which origin actually served the URL you checked, and where does that origin's configuration live? Framework middleware is the right home for application pages, but it cannot set headers for responses it never produces. For those, the CDN edge rules are usually the pragmatic single place — one rule set applied to every origin response, including static assets, without touching application code. For what each header should be in the first place, rather than where it is set, see our complete HTTP response and security headers guide — this post deliberately does not repeat its recommended values, so the two stay complementary instead of drifting apart.

Using an HTTP header checker the right way

A header checker is a diagnostic instrument, not a verdict, and the best HTTP header checker for this job is one that shows you exactly what it read: the URL entered, the status code, and the full response header list, without silently following redirects or substituting its own interpretation. Run it in this order: first the exact URL you care about, then the final URL after any redirect, then one host variant (www vs apex), then a 404 page on the same host. Four checks, two minutes, and the pattern across them names the cause before you touch any configuration. Run the free HTTP header checker on your site's final URL and compare it against a sub-page and an error page — if all three agree, your headers are genuinely set; if they disagree, the disagreement itself is the diagnosis, and the six causes above tell you where to look.

FAQ

Why does the checker say my security headers are missing when I added them?
Because the checker reads one exact response, and that response may be a redirect, a cached copy, an error page, or a route served by different configuration than the one you edited. Check the final URL, purge CDN cache, and compare a working route against the failing one before changing anything.

What is the best free HTTP header checker for diagnosing missing headers?
The best free HTTP header checker for this job shows the exact URL checked, the status code, and every response header, without silently following redirects — so you can tell a redirect response apart from the final page's response. It should also let you check any URL, including error pages and host variants, since the comparison across those responses is what diagnoses the problem.

Do I need to set headers on every page, or just the homepage?
Every response your site produces: pages, assets, API responses and error pages. Browsers enforce headers per response, not per site, and a checker (or an attacker) that requests any URL gets that URL's headers. Set them at a layer that covers everything — server config, framework middleware, or CDN edge rules.

Why do headers show on the homepage but not on other pages?
Almost always a configuration-level inheritance issue: on nginx, a location block with its own add_header directives drops the server-level headers for its routes. Repeat the header set in that block, include a shared snippet, or use add_header_inherit to merge levels.

Can I set security headers with HTML meta tags instead?
Only for Content-Security-Policy and Referrer-Policy, and headers are preferable even there. Strict-Transport-Security, X-Frame-Options and X-Content-Type-Options are ignored in meta tags — they must arrive as real HTTP response headers.

How long after deploying headers should I wait before re-checking?
Re-check the origin immediately, but allow CDN cache purge propagation before trusting an edge response — usually minutes, occasionally longer on large CDNs. If the origin shows the headers and the edge does not, the problem is cache, not configuration.