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.

Without HSTSSSL stripped
  1. 01user types example.com (no protocol)
  2. 02browser sends GET http://example.com
  3. 03attacker intercepts on the network, answers
  4. 04user lands on a fake page over plaintext
With HSTS preloadfirst request is HTTPS
  1. 01user types example.com (no protocol)
  2. 02browser already knows this domain is HTTPS-only
  3. 03browser sends GET https://example.com directly
  4. 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).

The header
Strict-Transport-Security:max-age=31536000includeSubDomainspreload
seconds to remember (1 year)

Required. The browser keeps the HSTS rule for this domain for this many seconds. Preload requires at least 31536000.

applies to every subdomain

Optional. Every subdomain of the host inherits the rule. Check every internal subdomain before setting this.

ship the rule into Chromium

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.

Staged rollouteach step is harder to undo
  1. Phase 1max-age=3005 minutes

    Start here. A bad header clears itself in minutes. Verify the response is present site-wide.

  2. Phase 2max-age=864001 day

    Still recoverable within hours if something goes wrong. Soak for a week.

  3. Phase 3max-age=315360001 year

    The Baseline. Any browser that visits once will not touch HTTP for a year, even if you roll the header back.

  4. Phase 4max-age=31536000; includeSubDomains1 year, every subdomain

    Every subdomain inherits the rule. Inventory first. Internal services still on HTTP will break.

  5. Phase 5max-age=63072000; includeSubDomains; preload2 years + preload list

    Submit at hstspreload.org. Once shipped in Chrome, every major browser picks it up. Removal takes months.

Preload removal takes months. Browsers ship the list baked in; they only update on their release cadence. Treat phase 5 as permanent.

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; preload on 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

setting preload + includeSubDomains on day one

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.

sending HSTS over HTTP

Browsers ignore it by spec. RFC 6797 forbids trusting a header that could itself have been forged. Send it only from HTTPS.

small max-age that never grows

A domain stuck at max-age=300 in production is a staging artefact. No preload eligibility, no first-visit protection. Raise it.

dropping the header after rollout

The browser forgets the rule when max-age expires from the last time it saw the header. Keep sending it on every response.

cert expires on a preloaded subdomain

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.