Skip to main content

Common problems

I set the HSTS header but it is not there

Response headers pass through a chain and only the last writer wins. How to find which layer is dropping or overwriting Strict-Transport-Security.

On this page

You added Strict-Transport-Security to the config, deployed, and the header is not in the response. Or it is there on one URL and gone on another.

Response headers are not a setting, they are the end of a pipeline. Your request passes through a CDN, maybe a load balancer, maybe a reverse proxy, then your application, and every one of those can add a header, replace one, or drop one. Only what survives the last hop is what the browser sees.

See what is actually returned

curl -sI https://example.com | grep -i strict-transport

If that comes back empty, the header is not reaching the edge. Check a few more paths before concluding anything, because partial coverage is extremely common:

for p in / /login /api/health /missing-page; do
  printf "%-16s %s\n" "$p" "$(curl -sI "https://example.com$p" | grep -i strict-transport || echo MISSING)"
done

Where it goes missing

LayerWhat it does
CDNHas its own header rules and can strip or replace origin headers entirely. Often the answer when the origin looks correct
Reverse proxyAn add_header or equivalent in an inner block can silently discard headers set in an outer one
Application frameworkSecurity middleware may set its own policy, overwriting yours with a shorter max-age
Route coverageConfigured on the main site block but not on redirects, error pages, or the API
Plain HTTP responsesSet on the port 80 response, where browsers ignore it completely

That fourth row deserves attention. A redirect from example.com to www.example.com is itself a response, and if it carries no HSTS policy then the apex domain never gets protected no matter what www sends.

Nginx has a specific trap

In nginx, add_header directives do not accumulate across blocks. If a location block declares any add_header, every add_header from the enclosing server block is discarded for that location.

So a policy set at server level disappears the moment an inner location adds a single header of its own. The always parameter is also needed if you want the header on error responses, not just successful ones.

Then check the policy you got back

Once the header appears, read the value rather than assuming. A framework default might be giving you a max-age of a few hundred seconds, which is technically present and practically meaningless.

Confirm the duration is what you configured, and that includeSubDomains is there only if you genuinely audited your subdomains. HSTS and HTTPS browser policy covers the directives and the rollout order, and browser security headers covers the neighboring headers that fail the same way.

Questions people ask

Why is my Strict-Transport-Security header missing when I configured it?

Because something later in the response chain removed or replaced it. A CDN, reverse proxy, or application framework can each add, overwrite, or strip headers, and only what survives the final hop reaches the browser.

Do browsers ignore HSTS sent over plain HTTP?

Yes, entirely. The policy is only honored when it arrives over a valid HTTPS connection. Setting it on your port 80 response accomplishes nothing.

Is the header sent on every response or just the first?

It should be sent on every HTTPS response. Many setups add it only to the main document route and miss redirects, error pages, and API responses, which is why a header can look present in one test and absent in another.

Last verified Sep 18, 2026

Try these checks on your own domain: Start a free scan. If a result needs a closer look, contact us.