HSTS
You redirect HTTP to HTTPS on your server. The user types example.com in the address bar. The browser sends GET http://example.com. Your server replies with a 301 to the HTTPS version. Safe enough.
Not if an attacker is on the network between the browser and your server. They answer the HTTP request themselves with a fake page. The user never touches your real server. SSL stripping.
HSTS fixes this. The header tells the browser: next time the user types example.com, do not send HTTP. Go straight to HTTPS. The redirect step no longer happens. There is no plaintext request to intercept.
The problem
A plain HTTP redirect protects the user only after the redirect arrives. The first request, before the 301, is still plaintext and forgeable. On any network the user does not control (café, hotel, airport, corporate NAT) that first request is the whole attack surface.
- 01user types example.com (no protocol)
- 02browser sends GET http://example.com
- 03attacker intercepts on the network, answers
- 04user lands on a fake page over plaintext
- 01user types example.com (no protocol)
- 02browser already knows this domain is HTTPS-only
- 03browser sends GET https://example.com directly
- 04encrypted connection, attacker cannot read it
HSTS closes the gap on every visit after the first. Preload closes it on the first visit too, by baking the promise into the browser itself. Specified in RFC 6797.
The header
One header, three directives. Send it on every HTTPS response. Browsers ignore it on HTTP responses (the spec says so; a MITM could otherwise forge it).
Required. The browser keeps the HSTS rule for this domain for this many seconds. Preload requires at least 31536000.
Optional. Every subdomain of the host inherits the rule. Check every internal subdomain before setting this.
Optional. Advertises that you want to be added to the browser preload list. Required to be eligible. Nearly irreversible.
The minimum useful value is max-age=31536000; includeSubDomains. Anything shorter will not qualify for preload and leaves a bigger first-visit window.
The rollout
HSTS is different from most headers. A mistake does not just degrade one page, it locks every browser out of any HTTP path for up to max-age seconds. Ratchet up slowly.
- Phase 1
max-age=3005 minutesStart here. A bad header clears itself in minutes. Verify the response is present site-wide.
- Phase 2
max-age=864001 dayStill recoverable within hours if something goes wrong. Soak for a week.
- Phase 3
max-age=315360001 yearThe Baseline. Any browser that visits once will not touch HTTP for a year, even if you roll the header back.
- Phase 4
max-age=31536000; includeSubDomains1 year, every subdomainEvery subdomain inherits the rule. Inventory first. Internal services still on HTTP will break.
- Phase 5
max-age=63072000; includeSubDomains; preload2 years + preload listSubmit at hstspreload.org. Once shipped in Chrome, every major browser picks it up. Removal takes months.
Each phase is a commitment. The browser remembers max-age seconds from the moment it sees the header. Raise the value only when you are confident every subdomain covered by it actually speaks HTTPS.
Preload
Even the longest max-age only helps browsers that have visited you at least once. A new user hitting your site for the first time still sends HTTP unless they typed https:// explicitly.
The browser preload list fixes that. Submit your domain at hstspreload.org and, once accepted, Chrome ships the rule in a static list. Firefox, Safari, and Edge pull from the same list. Every browser that ever downloads that list treats your domain as HTTPS-only from the first byte.
Acceptance requirements:
- Serve a valid cert on the apex and
www. - Redirect HTTP to HTTPS on port 80.
- Send
Strict-Transport-Security: max-age=31536000; includeSubDomains; preloadon HTTPS responses from the apex. - Every subdomain must actually work over HTTPS.
The one-way door
HSTS is the most irreversible thing a typical site ships. Once a browser has cached the rule, you cannot undo it for that user until either max-age elapses or you serve max-age=0 and they revisit you. Preload is worse: the list is baked into every browser release, and removal takes months to propagate.
This is not a reason to avoid HSTS. It is a reason to phase in, inventory every subdomain before setting includeSubDomains, and not submit to preload until the site has been stable for weeks.
Common mistakes
An internal subdomain on HTTP breaks for every visitor who pulls that preload list. Until the list updates, they cannot reach it over HTTP. Stage.
Browsers ignore it by spec. RFC 6797 forbids trusting a header that could itself have been forged. Send it only from HTTPS.
A domain stuck at max-age=300 in production is a staging artefact. No preload eligibility, no first-visit protection. Raise it.
The browser forgets the rule when max-age expires from the last time it saw the header. Keep sending it on every response.
Users cannot click through. HSTS removes the "accept risk" option. Renew certs automatically via ACME.
Check whether your domain ships HSTS, how long the max-age is, and whether you are on the preload list: scan your domain.