CrawlCheck

Guides · 2026-09-30 · By · 0 views

HSTS audit: missing header, preload ineligible, removed

How to read Strict-Transport-Security from outside, what the browser preload list adds and takes away, and why the scanner never tells you to preload.

Strict-Transport-Security tells a browser that has reached a site over HTTPS to refuse plain HTTP for max-age seconds. CrawlCheck reads the header from the homepage response and raises HSTS_MISSING when there is none, HSTS_PRELOAD_INELIGIBLE when the preload token is sent without a one-year max-age and includeSubDomains, and HSTS_PRELOAD_REMOVED when hstspreload.org records the domain as removed while the token is still sent. Preloading is deliberately not recommended: it takes months to undo.

Share of all scans carrying each finding named aboveHSTS_MISSING7.5%HSTS_PRELOAD_INELIGIBLE2%HSTS_PRELOAD_REMOVED0.3%Share of all scans carrying eachfinding named aboveHSTS_MISSING7.5%HSTS_PRELOAD_INELIGIBLE2%HSTS_PRELOAD_REMOVED0.3%
Read live from the same counters the dataset page uses, at the moment this page was served. Bars are scaled to the largest value shown, not to 100%.

Strict-Transport-Security is a one-line response header, and it is one of the few security settings a crawler can read without executing anything. It tells a browser that has already reached the site over HTTPS to refuse plain HTTP for the next max-age seconds. That is the whole mechanism. Everything that goes wrong with it goes wrong in one of three places: the header is not there, the header asks for something it does not qualify for, or the header was once good enough to enter a browser's built-in list and no longer is. This guide covers how to check each of those from the outside, what the scanner reports for each, and the one thing it deliberately does not recommend.

What the header actually does #

A browser that receives Strict-Transport-Security: max-age=31536000 over HTTPS remembers the host for a year. Every later navigation to http:// on that host is rewritten to https:// inside the browser, before any request leaves the machine. The user's first visit is still unprotected, which is the gap the preload list exists to close: a host on the list ships inside the browser as already-remembered, so even the first request is upgraded. Two directives change the scope. includeSubDomains extends the rule to every subdomain, and preload signals that the site owner consents to being added to the built-in list. Neither directive does anything on its own; preload in particular is a token that a separate submission reads, not an instruction a browser obeys.

How the scanner reads it #

CrawlCheck reads the header from the homepage response it already holds. No extra fetch is made, so what it reports is exactly what the first request received. If the homepage answered over HTTPS and carried no Strict-Transport-Security header at all, the report raises HSTS_MISSING, which fires on 7.5% of scans. If the header is present, the scanner parses max-age, looks for includeSubDomains and looks for the preload token, then decides one question: does this header qualify for the preload list? The list's own requirements are fixed. max-age must be at least 31,536,000 seconds, includeSubDomains must be present, and the preload token must be present.

A header that sends the preload token without meeting the other two requirements raises HSTS_PRELOAD_INELIGIBLE (2% of scans). The finding text names which requirement is missing, in the header's own numbers: a max-age of 15,768,000 is reported as 182 days against a required 365. The scanner then goes one step further than the header, because the header alone cannot tell you whether the domain is on the list.

The list is checked, not assumed #

hstspreload.org keeps a public status for every domain: unknown, pending, preloaded or removed. The scanner asks it. Three outcomes matter. A domain recorded as preloaded whose header no longer qualifies is the serious case: the browser list will drop it at a later build, and the drop is silent. A domain recorded as pending whose header cannot back the submission will be rejected as it stands, which is only a problem if the submission was yours. A domain recorded as removed that still sends the preload token raises HSTS_PRELOAD_REMOVED (0.3% of scans): the token is a promise the list no longer holds.

Header sentList statusReport
none, over HTTPSanyHSTS_MISSING
max-age under a year, no preload tokenunknownnoted, no finding
preload token, max-age under a yearunknownHSTS_PRELOAD_INELIGIBLE (what is short, in days)
preload token, no includeSubDomainspendingHSTS_PRELOAD_INELIGIBLE — submission will be rejected
header no longer qualifiespreloadedHSTS_PRELOAD_INELIGIBLE at higher severity — entry will be dropped
preload token presentremovedHSTS_PRELOAD_REMOVED

Most security checklists treat preload as the finish line. This one does not, and the reason is in the scanner's own source comment: preload takes months to undo. Once a domain is in the browser list, every subdomain must serve HTTPS for as long as the entry lives, and the entry lives in shipped browser binaries. A subdomain that later needs plain HTTP for a hardware device, a legacy vendor or an internal tool cannot have it. Removal is requested through the same site, but the removal propagates only as browsers update, which takes the better part of a year for the long tail. The scanner therefore never raises a finding for not preloading. The only findings with teeth are the ones where a preload token has been sent that the header cannot support, because that is a claim the list will act on.

How to check it yourself #

Fetch the homepage over HTTPS with curl -sI https://example.com/ and read the strict-transport-security line. Then confirm three things: the value of max-age in seconds, whether includeSubDomains is present, and whether preload is present. Then open hstspreload.org, enter the domain and read the status line. If the status is preloaded, the header you just read must still qualify, or the entry is on borrowed time. If the status is pending and you did not submit it, nothing needs doing. A free generator that builds the header from your answers to those three questions is at /api/fix/hsts (add ?domain= and your host); it refuses to add the preload token unless the other two requirements are met.

Fixing each case #

For HSTS_MISSING, add the header to the HTTPS response only. Sending it over plain HTTP is ignored by specification and confuses some proxies. Start with a short max-age such as 300 seconds while you confirm every subdomain and every redirect chain works over HTTPS, then raise it. For HSTS_PRELOAD_INELIGIBLE, the fix depends on intent. If you want preload, raise max-age to 31536000 and add includeSubDomains; if you do not, remove the preload token, because sending it without qualifying is the worst of both. For HSTS_PRELOAD_REMOVED, remove the token; a removed entry cannot be restored by continuing to send it, only by a fresh submission with a qualifying header.

What this check does not cover #

The scanner reads the homepage only. A subdomain that serves a different header, a redirect from http:// that lands somewhere other than the HTTPS homepage, and the actual list contents inside any particular browser build are all outside it. The status-code guide covers the redirect side. The rest of the security headers the report reads are described on the security page, and the share of scans carrying each finding above is on the dataset page, recomputed from the live corpus rather than typed into this guide.

Every figure above came out of this scanner.

Point it at your own domain and see the same measurements, free.

Scan a domain — free

The main product

Found this on your own site? We fix it for $749.

Scan free to see where you stand. The fix is one site, every finding implemented and re-measured, with a sealed before and after.

Questions this post answers

Does HSTS protect a visitor's first request?

No. The header is only read after a successful HTTPS response, so the first plain-HTTP request is unprotected. The browser preload list exists to cover that first request, at the cost of a commitment that takes months to reverse.

What does max-age need to be for the preload list?

At least 31,536,000 seconds, one year, together with includeSubDomains and the preload token. A shorter max-age with the preload token present is reported as ineligible, with the shortfall stated in days.

Is it a finding if my site has no preload token?

No. The scanner does not recommend preloading and never raises a finding for its absence. It raises findings only when a preload token is sent that the header cannot support, or when the public list already records the domain and the header no longer qualifies.

Why does the scanner query hstspreload.org?

Because the header alone cannot say whether the domain is on the list. A domain that is preloaded and later weakens its header will be dropped silently at a future list build; only the list status reveals that.

Should the header be sent over plain HTTP too?

No. Browsers ignore Strict-Transport-Security received over HTTP by specification. Send it only on HTTPS responses, and make the HTTP side a redirect to HTTPS.

Where can I see how common each HSTS finding is?

On the dataset page, which recomputes the share of scans carrying each finding code from the live corpus.

Related findings

How anything measured in this article was measured15client identitiesone second, one address5machine filesapex and www114named agentsresolved from robots.txt24sections scoredreach, read, quoteHow anything measured here was measured15 client identities5 machine files114 named agents24 sections scoredone second, one addressapex and wwwresolved from robots.txtreach, read, quote
No account, nothing installed, and the same sequence on every domain — which is what makes one scan comparable to another. Run it on your own site.

Comments

Comments are read before they appear. Nothing is published automatically, and no account is needed.

Writing about this? Facts, live figures and marks — every number on that page is dated and traceable to a scan.

All guides · The dataset · How the dataset works