What the header does
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
When a browser receives this over HTTPS, it remembers for max-age seconds that the host is HTTPS-only. For that period it rewrites any http:// link, bookmark or typed address for the host to https:// before making the request, so the first insecure request that a redirect would normally fix never happens, and it turns certificate errors into hard failures with no click-through. The header is ignored when received over HTTP, and ignored if the certificate was not valid, so an attacker cannot set it for you.
includeSubDomains extends the rule to every subdomain. preload is a signal that you consent to being added to the browsers' built-in list; on its own it does nothing.
What preloading adds
HSTS has a first-visit gap: a browser that has never seen the header will still make one plain HTTP request, and that one request is where a network attacker can intervene. The preload list, maintained by Chrome and consumed by Firefox, Safari and Edge, closes the gap by shipping your domain inside the browser as HTTPS-only from the start. You submit at hstspreload.org; inclusion takes weeks to reach stable releases, and removal takes longer.
The list has requirements, and they are the source of most of the pain:
- A valid certificate, and HTTP on port 80 must redirect to HTTPS on the same host.
- The header on the HTTPS response must have
max-ageof at least one year (31536000), andincludeSubDomains, andpreload. - It is the whole registered domain that is preloaded, not a hostname. Submitting
www.example.comis not possible; you submitexample.com, and withincludeSubDomainsthat covers everything under it.
The ways it locks you out
Every one of these is something I have seen or been asked to help unpick.
- A subdomain that only speaks HTTP. The intranet host, the printer, the development box, the vendor's status page you CNAME'd, the router admin page at
gw.example.com. WithincludeSubDomainsevery browser that has seen the header for the apex, or that has the preload list, will refuse to load them over HTTP and there is no override. If they cannot get a certificate, they are unreachable by name until the policy expires or they move to another domain. - A subdomain with a certificate that is not quite valid. Self-signed on an internal tool, expired on a forgotten service, wrong name on a shared host. Normally users click through the warning. Under HSTS the warning has no proceed button.
- Mail and other non-web hostnames are fine, because HSTS is a browser mechanism, but the moment someone browses to
mail.example.comto reach a webmail page that only listens on 80, see above. - Preload cannot be turned off quickly. Removing the header stops new visitors being told, but every browser that already knows keeps enforcing for the remaining
max-age, and every browser with the preload list enforces until a release ships without you, which is months. A domain that is sold, repurposed, or moved to a host that cannot do HTTPS for some subdomain is stuck. - Testing on the real domain. Set a one-year header while experimenting, discover a problem, and your own browser now refuses to let you see the HTTP version for a year. Chrome has a page (
chrome://net-internals/#hsts) to delete an entry; your users do not.
The checklist, in order
- Inventory every hostname under the domain, including ones that only exist internally and ones delegated to third parties. Check each one answers on HTTPS with a valid certificate. The SSL/TLS certificate checker does one host at a time; a list and a loop over the text output does the rest.
- Fix the HTTP to HTTPS redirect on the apex and on
www. It should be a 301 (or 308), one hop, to the same hostname over HTTPS, and the HTTPS response must carry the HSTS header. The redirect checker shows the chain and the security headers scorecard shows the header and parses its directives. - Start small.
max-age=300, noincludeSubDomains. Five minutes is long enough to see it work and short enough that a mistake costs nothing. Live with it for a day. - Add
includeSubDomainsat the short max-age, and wait a week. This is when the forgotten HTTP-only subdomain surfaces, and at five minutes you can back it out. - Raise
max-agein steps: a day, a week, a month, then a year. Each step is a commitment for that long. - Add
preloadand submit, only once you have been at a year with subdomains for a while without incident, and only if you accept that the domain is HTTPS-only for the foreseeable future. For most public sites that is the right call. For a domain that hosts anything you do not fully control, think twice.
Things worth knowing
- The header only needs to be on responses that a browser will see, but it costs nothing to send it on everything over HTTPS, including redirects and errors. Send it on the apex and on
wwwboth, since users will land on either first. - HSTS does not replace the redirect. Non-browser clients ignore it, and browsers need the redirect to reach the HTTPS response that carries the header in the first place.
- A CDN or load balancer in front of you may be adding or stripping the header. Check the response the internet sees, not the one your origin sends.
- The domain health checker reports the HTTPS redirect, its status code and the HSTS header on the apex in its HTTP pillar, which is a quick way to confirm the first two steps after a change.