You're viewing the readable version of this site. The interactive extras (search, diagrams, read-aloud) need JavaScript and a current browser. Enable JavaScript; if it is already enabled, update your browser.

Notes · Dissecting Real Systems

growing

HSTS: It's Time to Go HTTPS-Only

The header that makes HTTPS non-negotiable — no SSL stripping, no click-through, no fast rollback. Turn it on. Then think carefully before you preload, because that part you cannot take back.

· · 27 min read

security, http, tls, hsts, web, browsers, privacy, dissecting-systems

If a web application issues an HSTS Policy, then it is implicitly opting into the "no user recourse" approach, whereby all certificate errors or warnings cause a connection termination.

RFC 6797, §12.1, "No User Recourse"

Cite this
APA
Mangalapilly, Y. J. (2026, August). HSTS: It's Time to Go HTTPS-Only. Saṃhitā Notes. https://yesudeep.com/blog/hsts-its-time-to-go-https-only/
BibTeX
@online{mangalapilly2026hsts,
  author  = {Yesudeep Jose Mangalapilly},
  title   = {HSTS: It's Time to Go HTTPS-Only},
  journal = {Sa\d{m}hit\=a Notes},
  year    = {2026},
  month   = {August},
  url     = {https://yesudeep.com/blog/hsts-its-time-to-go-https-only/},
  urldate = {2026-08-12},
}
Plain
Yesudeep Jose Mangalapilly. “HSTS: It's Time to Go HTTPS-Only.” Saṃhitā Notes, 2026. https://yesudeep.com/blog/hsts-its-time-to-go-https-only/.
RIS
TY  - ELEC
AU  - Mangalapilly, Yesudeep Jose
TI  - HSTS: It's Time to Go HTTPS-Only
T2  - Saṃhitā Notes
PY  - 2026
UR  - https://yesudeep.com/blog/hsts-its-time-to-go-https-only/
Y2  - 2026-08-12
ER  - 

One header, and the commitment hiding inside it. This piece in the web security series is the first about a control you cannot roll back. By the end you'll know what a browser actually does when it receives Strict-Transport-Security, why the very first visit to your site is the one request the header cannot protect, what the preload list physically is, why max-age=0 retracts one kind of HSTS policy and does nothing at all to the other, and how to decide whether preloading is a commitment you want — now that browsers have started upgrading to HTTPS on their own.

The recommendation, stated up front so the rest can be read as the reasoning behind it: serve HSTS. If your site is already HTTPS-only in practice — and in 2026 it almost certainly is — the header costs nothing you were not already paying and closes an attack the redirect you are relying on cannot. Ramp max-age in stages, add includeSubDomains once you have actually inventoried your subdomains, and treat preloading as a separate decision made deliberately rather than as the last step of the same afternoon. The first three are ordinary operational work. The fourth is the one you cannot take back, and most of this article is about telling them apart.

TLS, and why everyone still says SSL. TLS — Transport Layer Security — is the protocol under the s in HTTPS. It began as Netscape's SSL, and the rename happened at version 1.0: RFC 2246 notes that TLS 1.0 carries the wire version { 3, 1 } because it is "a minor modification to the SSL 3.0 protocol." TLS 1.0 is SSL 3.1, which is why the old name never died.

Every version of SSL is now forbidden, in identical language. RFC 7568: "SSLv3 MUST NOT be used." RFC 8996 (BCP 195) repeats it for TLS 1.0 and 1.1. What you should be running is TLS 1.3, specified in RFC 9846 — published July 2026, replacing the RFC 8446 that most documentation still cites.

Note

What TLS actually promises, in the specification's own three words: authentication ("the server side of the channel is always authenticated; the client side is optionally authenticated"), confidentiality ("data sent over the channel after establishment is only visible to the endpoints"), and integrity ("cannot be modified by attackers without detection").

Notice the first one. TLS authenticates the server — it says nothing about whether the client will bother to use TLS at all. That gap is the entire reason this article exists. And confidentiality has a documented limit: TLS "does not hide the length of the data it transmits."

A request to http://bank.example is a request an attacker on the network can read and rewrite. The classical fix is a redirect: the server answers port 80 with 301 https://bank.example, and the browser goes to the secure address. It works, and it is not enough, because the redirect arrives after the plain-HTTP request has already left the device. An attacker sitting between the two does not have to break TLS; they only have to answer first, keep the connection on http://, and proxy the site themselves. The user sees a working page. The attack is SSL stripping, and no amount of server-side redirecting fixes it, because the fix is being delivered over the very channel under attack.

SSL stripping — a network attacker intercepts a plain-HTTP request and serves the site over HTTP while talking to the real server over HTTPS. The victim never sees a certificate warning, because from the browser's point of view nothing secure was ever attempted. Moxie Marlinspike introduced the technique in 2009, along with a tool that automated it.

RFC 6797 answers this by moving the decision into the browser, before any request is made. A server that has been reached securely once can say:

Strict-Transport-Security: max-age=31536000; includeSubDomains

and the browser records the host as a Known HSTS Host. From then on, for max-age seconds, it rewrites http:// URLs for that host to https:// locally — before anything leaves the machine. There is no plain-HTTP request for an attacker to answer, because there is no plain-HTTP request.

A redirect tells the browser where to go after it has already spoken. HSTS changes what the browser says in the first place.

What the browser actually does

The spec is unusually crisp about the mechanics, and three of its rules explain most of HSTS's real-world behavior.

On receipt (§8.1), a browser that gets the header over a secure transport, with no certificate errors, must note the host and its max-age. Two companion rules matter more than they look:

  • max-age=0 is the retraction. The UA "MUST remove its cached HSTS Policy information (including the includeSubDomains directive, if asserted) if the HSTS Host is known." This is how you turn it off — and remember it, because it will not mean what you expect once preloading enters the picture.
  • The first header wins. If a response carries more than one Strict-Transport-Security field, "the UA MUST process only the first such header field." Worth holding onto: a Content Security Policy composes the opposite way, and the piece that closes this series is largely about the consequences of that difference.
  • Headers over plain HTTP are ignored entirely. An attacker who can rewrite your HTTP traffic cannot use it to install or extend an HSTS policy.

max-age — how many seconds the browser should keep treating this host as HTTPS-only. It is a sliding window refreshed on every secure visit, not a countdown from first contact: an active reader effectively never lets it lapse, while a browser that stops visiting forgets on schedule.

On certificate errors (§8.4), the browser "MUST terminate the connection" — and §12.1 removes the escape hatch, saying the user "should not be presented with a dialog giving her the option to proceed." That sentence is the whole security value of HSTS and its whole operational risk in one line.

Warning

HSTS converts an expired certificate from a warning into an outage. Without it, a visitor sees an interstitial and can click through; the site is broken but reachable, and your support channel fills with complaints rather than silence. With HSTS, there is no interstitial and no click-through — the connection simply fails for everyone, and it keeps failing until the certificate is fixed. Renewal automation stops being hygiene and becomes load-bearing infrastructure.

What a certificate actually claims, and how to get one free. An X.509 certificate binds a public key to a subject, and the binding is asserted by a Certificate Authority's signature. The claim is much narrower than most people assume: a domain-validated certificate attests control of a domain name and nothing else — not that the operator is honest, solvent, or who they say they are.

That narrowness is structural rather than conventional. The CA/Browser Forum Baseline Requirements permit a DV certificate to carry a country name, discourage a common name, and mark every other identity attribute MUST NOT. There is nowhere to put the operator's identity. The same document adds that all subscriber certificate types "provide the same level of assurance of the device identity" — so a paid OV or EV certificate buys you branding and paperwork, not stronger domain authentication.

Getting one costs nothing. Let's Encrypt, run by the nonprofit ISRG, is "a free, automated, and open Certificate Authority." Issuance is automated by ACME (RFC 8555), a protocol whose entire purpose is letting "a CA and an applicant… automate the process of verification and certificate issuance."

Note

The shortest path, by situation.

  • Your own server, starting fresh — install Caddy. Its documentation is not exaggerating: "By default, Caddy serves all sites over HTTPS… It just works!" Point DNS at the box, open 80 and 443, and there is no certificate step.
  • An existing nginx or ApacheCertbot, which is Let's Encrypt's own recommendation, or any of the other ACME clients.
  • Behind CloudflareUniversal SSL is free on every plan and requires no separate enablement on supported zones.

Certificates are also getting shorter, deliberately. On 2026-08-12, Let's Encrypt issued 90-day certificates and had published a schedule taking that to 45 by February 2028, tracking an industry cap that reaches 47 days in 2029; six-day certificates have been generally available since January 2026. Their reasoning is worth borrowing: short lifetimes "limit damage from mis-issuance and key compromise" and "encourage automation, which is absolutely essential for ease-of-use and reliability."

Warning

If you terminate TLS at a CDN, check which leg is actually encrypted. Cloudflare's SSL modes are a real trap here, and its own documentation states it plainly: under Flexible, "traffic from visitors to Cloudflare can be encrypted via HTTPS, but traffic from Cloudflare to the origin server is not."

A site in Flexible mode showing a padlock is telling the truth about the first hop and saying nothing about the second. Serving HSTS from there is the exact case where the header promises more than the deployment delivers: the browser is instructed to always use TLS for your origin, and one leg of the path is still plaintext.

Full is barely better — it encrypts the origin leg without validating the certificate, so it is encrypted but unauthenticated. Only Full (strict) makes an HSTS promise honest end to end, and Cloudflare's free Origin CA certificates exist precisely so that costs nothing.

Think of a building that has both a secure entrance and an unlocked side door. Posting a sign at the side door saying "please use the main entrance" helps politely, but anyone standing at that door can take the sign down and wave you in themselves.

HSTS is different: it tells you, once, to write in your own notebook that this building has no side door. After that you walk to the main entrance directly, and nobody standing at the side door can talk you into using it, because you never go there to be talked to. The catch is the same as any note in a notebook — you had to get told once, safely, before it did any good. And if the main entrance later jams, you have written down that there is no other way in.

The visit the header cannot protect

There is one request HSTS structurally cannot help with: the first one. The browser learns the rule from a response, so it must have already sent a request to receive it — and if the user typed bank.example or followed an http:// link, that request went out in the clear.

The RFC names this itself, in §14.6, Bootstrap MITM Vulnerability: a user who reaches "an unknown HSTS Host using an http URI rather than an https URI" makes an initial interaction that "is vulnerable to various attacks."

The gap and what closes it. A header-taught policy needs one safe round trip before it protects anything; a preloaded policy is present before the browser has ever contacted the site.

That gap is not hypothetical, and the largest recorded use of it did not target the site that left it open. In March 2015 an on-path system the Citizen Lab named the Great Cannon began intercepting requests for analytics JavaScript served by Baidu over plaintext HTTP. Roughly 1.75% of those requests were answered with a substituted script instead of the real one. The substituted script ran in the browsers of ordinary visitors and directed them, without their knowledge, at GitHub and GreatFire — a denial-of- service attack assembled out of other people's readers.

Baidu was not compromised. Its analytics file was simply reachable in the clear, so anyone positioned between the reader and the server could answer first. Every site embedding that file donated its audience. HTTPS removes the ability to answer first; HSTS removes the plaintext request that gives the attacker the opening.

Trust on first use (TOFU) — a security model that accepts the first contact as authentic and detects tampering only afterward. SSH host keys work this way. The weakness is always the same: an attacker present at the very first contact is indistinguishable from the real thing. Learn more.

The preload list closes that gap by inverting where the rule comes from. Instead of your server teaching each browser, the browser already knows, because your domain shipped inside it.

What preloading actually eliminates

It is worth being blunt about the payoff, because it is the strongest thing that can be said for any of this.

Against a network attacker — the classical one, sitting on the airport Wi-Fi or the hotel router, running SSL stripping — a preloaded domain gives them nothing to work with. Every attack in that family needs the same first move: get one plain-HTTP request, and answer it. A header-taught policy denies them that move on every visit except the first. A preloaded policy denies it on the first one too, because the browser rewrites the URL before a packet exists, using a rule that arrived with the software rather than over the network the attacker controls.

For a preloaded domain, SSL stripping has no foothold left. There is no plain-HTTP request for an attacker to intercept, because the browser never makes one — not even once, not even on a network that has been hostile since before the user opened the laptop.

That is a genuinely strong property, and it is why the list exists at all. It also has a pleasant second-order effect: the http:// to https:// redirect you serve on port 80 stops being a security control and becomes a courtesy for clients that are not browsers. Nothing a preloaded browser does depends on it.

Important

To be precise about the boundary, preloading eliminates the transport downgrade — the attacker who keeps the connection on http://. It does nothing about an attacker who obtains a certificate your browser will accept, whether through a mis-issuance, a compromised CA, or a device with an attacker-installed root. That is a different attack with different defenses, and it is why Certificate Transparency exists alongside HSTS rather than being made redundant by it.

What you have retired is the easy attack — the one that needs no certificate, no CA, and no privileged access to the device. That is most of them, and it is worth retiring. It is not all of them.

What the preload list actually is

It is worth being concrete, because "the preload list" sounds like a service and is in fact a file.

Chromium keeps it at net/http/transport_security_state_static.json, in the browser's own source tree. Fetched on 2026-08-06 from main, that file is 10,521,862 bytes — about ten and a half megabytes of JSON — holding 94,628 entries. It is compiled into a lookup structure at build time and ships in the binary. Firefox, Safari, and Edge maintain lists derived from it.

Each entry carries a policy field recording how it got there, and the distribution is a fossil record of the rules tightening over time:

The 94,628 preload entries by policy, from the Chromium source file on 2026-08-06. The two bulk- buckets date from different eras of the submission requirements — bulk-18-weeks entries were admitted when a shorter max-age was acceptable.
policy entries what it means
bulk-1-year 84,352 submitted under the current rule: max-age of at least a year
public-suffix-requested 4,954 a registry asked for its whole suffix
bulk-18-weeks 4,552 admitted under the older, shorter max-age bar
custom 394 hand-managed entries
bulk-legacy 215 predates the bulk process
google 104 Google-operated domains
public-suffix 57 whole suffixes, including .app, .dev, .bank

Those last two rows are worth a pause: entire top-level domains are preloaded, so every name under .dev or .app is HTTPS-only in supporting browsers whether or not its owner ever configured anything. If you have wondered why a local development host on .dev refuses to load over HTTP, that is the reason, and it is not something your machine can be persuaded out of.

The submission site states the requirements plainly: serve a valid certificate; redirect HTTP to HTTPS on the same host if you listen on port 80; serve all subdomains over HTTPS; and serve a header on the base domain with max-age of at least 31536000, plus includeSubDomains and preload.

Why includeSubDomains exists, and what it costs

The usual explanation — "so your subdomains are protected too" — is not the RFC's. §14.4 gives a sharper reason: cookies. A cookie set with a Domain attribute is sent to every subdomain, and an attacker who can serve plain HTTP from any name under your domain — including one they registered themselves on a wildcard DNS setup — can plant or read cookies that your secure origin will honor. Locking only www leaves that door open. The directive is therefore less about protecting subdomains from each other than about protecting the cookie jar they share.

Warning

The subdomain commitment is the pitfall that bites hardest, and it bites late. includeSubDomains applies to names that do not exist yet. The internal tool someone stands up on metrics.example.com two years from now, over plain HTTP on a private network, will not be insecure — it will be unreachable, with no click-through, in every browser that has ever visited your main site. Audit the subdomains you have before asserting it; then remember you have also constrained every subdomain you will ever create.

Two policies wearing the same header

Here is the distinction that most treatments blur, and the single most useful thing to take away.

A header-taught HSTS policy is cached state. It lives in one browser profile on one device, it expires, and max-age=0 clears it. It is revocable, per-visitor, on your schedule.

A preloaded policy is not state at all. It was compiled into the browser before that browser ever ran. There is no cached entry for max-age=0 to clear, so sending it accomplishes exactly nothing for preloaded visitors.

The same header, two lifetimes. Retraction follows the path the policy arrived by — and only one of those paths is yours to walk.

max-age=0 retracts a policy the browser learned. It cannot retract a policy the browser was born with.

Removal from the list is a separate process: submit at the removal form, serve a valid HSTS header without preload, and wait. The site's own estimate is that removal "may take 6-12 weeks to reach most Chrome users, and may take longer for other browsers." You are not waiting on a cache to expire; you are waiting on release trains.

Prediction checkpoint. Commit before reading on.

The identifier hiding in the policy store

There is a second consequence of a persistent, per-domain, per-browser store that survives clearing cookies: it can hold bits.

The attack is simple enough to describe in a sentence. A tracker controls 32 domains. On your first visit it loads a chosen subset of them over HTTPS, so your browser records HSTS policies for exactly those. Later, from anywhere, it asks your browser to load all 32 over http:// and measures which ones get upgraded locally. The upgraded ones are the subset it chose — a 32-bit identifier written into a store that clearing cookies does not touch.

HSTS state as a bit vector. Each controlled domain answers one yes/no question, and the answers were chosen by whoever set them.

The RFC anticipated this. §14.9, "Creative Manipulation of HSTS Policy Store," describes the technique and closes by noting it "could potentially be abused as yet another form of 'web tracking'." WebKit's writeup is the clearest primary account of it being used in the wild, and states the arithmetic directly: "By registering some large number of domains (e.g., 32 or more), and forcing resource loads from a controlled subset of those domains, they can create a large enough vector of bits to uniquely represent each site visitor."

Browsers responded by scoping the state — restricting what a policy can be recorded for, and ignoring HSTS upgrades for third-party subresources from cookie-blocked domains, which as WebKit puts it renders the technique "a bit string consisting only of zeroes."

Note

The preload list reveals a recurring shape worth naming: a mechanism designed to be persistent and hard to clear is, by construction, a good place to store an identifier. The property that makes HSTS resist attackers is the same property that made it attractive to trackers. Nothing was mis-designed; two goals simply pointed the same feature in opposite directions.

What HSTS still buys in 2026

The obvious objection to all of this is that browsers have started fixing the problem themselves. Chrome's "Always Use Secure Connections" is scheduled to be on by default in Chrome 154, October 2026, phased in from Chrome 147 in April, with private and intranet sites treated differently. If the browser upgrades everything anyway, why declare anything?

Because the two mechanisms differ in exactly the place that matters.

Two ways to get an HTTPS-only browser, and what separates them.
HTTPS-First / auto-upgrade HSTS
Who decides the browser, as a default the site, as a declaration
Can the user override yes — it warns, and they may proceed no — §12.1 removes the option
Applies to that browser's users any browser implementing the RFC
On certificate failure user may click through connection terminated

Auto-upgrade is a default; HSTS is a commitment. A default protects people who do nothing, which is most people, and that is genuinely valuable. But it remains overridable, and an overridable protection is not one you can build a guarantee on. The no-click-through property is the part that survives, and it is the reason HSTS is not made redundant by browsers doing the right thing on their own.

Important

What auto-upgrade does change is the honest cost-benefit of preloading. The first-visit gap was always preload's main justification, and browser defaults in 2026 close much of it for much of the population. That does not make preloading wrong — the guarantee is stronger and applies to browsers whose defaults you do not control — but the argument for accepting an irreversible commitment is weaker in 2026 than it was in 2016. If you preload in 2026, do it because you want the guarantee, not because you fear the gap.

Deploying it without trapping yourself

The rollout is ordered by reversibility: everything cheap to undo comes first, and the one-way door is last.

  1. Serve HTTPS everywhere, and inventory your subdomains. Not "the ones in production" — every name that resolves, including internal tooling and things a colleague set up in 2019. Teams skip the subdomain inventory, and it is the step that decides whether includeSubDomains is safe.
  2. Start with a short max-age and no includeSubDomains. Something like max-age=300. If it breaks, the blast radius expires in five minutes.
  3. Raise max-age in stages — a day, a week, a few months — pausing long enough at each step for real traffic to find the problem.
  4. Add includeSubDomains once the inventory is clean, and treat it as a standing constraint on every future subdomain rather than a one-time decision.
  5. Only then consider preload — and treat submission as a distinct, deliberate act, not the natural end of the sequence. It is the step that retires SSL stripping outright, and the step you cannot walk back; those are both true at once, which is exactly why it deserves its own decision rather than the momentum of the four before it.

Important

Verify what browsers actually receive, not what your configuration says. A CDN or edge proxy may set, override, or strip this header, and a dashboard toggle can silently replace the value your repository defines. The failure mode is a divergence nobody notices until a scanner disagrees with the config file — and for a header whose whole job is a long-lived promise, drift between intent and reality is the bug.

Since that override is the trap, it is worth seeing the surface where it happens. Here is the same policy expressed as a CDN's settings panel — in this case Cloudflare's, under SSL/TLS → Edge Certificates → HSTS:

The deployment ladder as four toggles. max-age, includeSubDomains and preload appear here as separate switches, each with its own warning — and this dialog, not the repository, is what proxied traffic actually receives. The Save button is a policy change with a twelve-month tail.
The deployment ladder as four toggles. max-age, includeSubDomains and preload appear here as separate switches, each with its own warning — and this dialog, not the repository, is what proxied traffic actually receives. The Save button is a policy change with a twelve-month tail.

Note

Two things are worth noticing in a panel like this one. First, the deployment ladder is compressed into switches that are equally easy to flick, which flattens the distinction between the reversible steps and the one that is not — the interface makes preload look exactly as casual as max-age. Second, the panel bundles an unrelated header: nosniff has nothing to do with transport security and is here because both live under the same "security settings" heading in the product's information architecture. Neither observation is a criticism of the tool. Both are reasons to read the wire afterward rather than trusting the form you just submitted.

The three requirements, and which one is the door

Serving preload in the header, and being on the list, are not the same thing. The token is an assertion of intent that the submission process checks; nothing happens until you submit. That gives a useful staging point: you can serve the full header, satisfy every requirement, and verify it in production for as long as you like — while remaining entirely reversible, because you have not submitted. Deleting the token is a deploy. Deleting the list entry is a season.

The submission site will tell you plainly where you stand, and it refuses to take you at your word. Here is this domain, checked while writing this article:

The eligibility check refusing a submission. yesudeep.com is not preloaded and not yet eligible: the live response carried max-age and includeSubDomains but not the preload token, so the form declined it. The check reads what the server actually sends, not what its configuration intends.
The eligibility check refusing a submission. yesudeep.com is not preloaded and not yet eligible: the live response carried max-age and includeSubDomains but not the preload token, so the form declined it. The check reads what the server actually sends, not what its configuration intends.

That failure is worth dwelling on, because it was not a typo — it was the dashboard-override trap, caught in the act. The site's repository declared the full header, preload token included. The value browsers received did not have it, because the CDN's own HSTS setting takes precedence and had not been changed to match. Two sources of truth, one of them authoritative, and the disagreement is invisible until something reads the wire.

The repair is not a deploy. Editing the file again would have changed nothing, because the file was never what browsers were reading; the fix was to change the setting that actually wins and then re-read the wire to confirm. After flipping the CDN's own preload toggle, the same check answers differently:

The same check after the override was corrected. The verdict has flipped to eligible and the form now offers the acknowledgement. The two screenshots differ by one dashboard setting, not by one commit — which is the whole lesson about where the header actually comes from.
The same check after the override was corrected. The verdict has flipped to eligible and the form now offers the acknowledgement. The two screenshots differ by one dashboard setting, not by one commit — which is the whole lesson about where the header actually comes from.

Read the acknowledgement it asks you to tick, because it states the commitment more honestly than most prose about HSTS manages to. Preloading yesudeep.com will prevent *.yesudeep.com and *.*.yesudeep.com — every subdomain and every nested subdomain, including the ones that do not exist yet — from being reached without a valid certificate. The checkbox is the one-way door, written out as a wildcard.

I ticked it. The domain was submitted while I finished the article, and the list's API reported it as pending with no errors and no warnings on 2026-08-12 — which means it is queued for inclusion in a future Chromium release and will propagate to the browsers that consume that list from there. Nothing about that is fast, and that is the point: the commitment was made in a second, and unmaking it would be measured in release trains.

Note

pending is its own state, and worth understanding before you submit. The domain is accepted and queued but not yet compiled into anyone's browser, so while the entry is pending, the protection comes entirely from the header — exactly as it was the day before submission. The list entry starts mattering when a release carrying it reaches users, and stops being retractable at the same moment.

Note

Which makes the eligibility check quietly valuable beyond its stated purpose: it is a free, external observation of what your server actually emits. Run it even if you have no intention of submitting. A domain that believes it is sending a header it is not sending is a small class of bug with no other alarm attached to it.

The shape of the whole thing

Most of web security is a negotiation you can reopen. Policies are tightened and loosened, headers are added and removed, mistakes are absorbed by a deploy. HSTS is the exception, and it is the exception on purpose: a promise the browser will renegotiate is a promise an attacker can renegotiate.

What that buys is a guarantee no network attacker can strip and no user can click past. What it costs is your ability to change your mind — bounded by max-age for a header, and by someone else's release schedule for a preload entry. The mechanism is not subtle. The commitment is, and it is worth knowing which of the two you are signing.

None of which is an argument against turning it on. Serving HSTS on a site that already speaks HTTPS is close to free, and it converts a redirect an attacker can answer into an instruction the browser follows before the attacker gets a turn. Go HTTPS-only, and mean it. Just notice that the last step in the sequence — the one that puts your domain in the browser's binary — is a different kind of decision than the three before it, and give it the deliberation it asks for rather than the momentum of a checklist.

Lessons

  • A redirect arrives too late. It is delivered over the channel the attack already controls; HSTS moves the decision into the browser, before the request is made.
  • The first visit is the gap. A header can only teach a browser after one safe round trip — RFC 6797 §14.6 calls this the bootstrap MITM vulnerability, and it is the trust-on-first-use problem in another costume.
  • includeSubDomains is about the shared cookie jar, per §14.4 — not merely about protecting subdomains. It also binds every subdomain you have not created yet.
  • There is no report-only mode. You cannot dry-run this header, which makes the staged max-age rollout the only available substitute for one.
  • Certificate expiry becomes an outage, not a warning (§8.4, §12.1). No interstitial, no click-through, no user recourse.
  • The preload list is a 10.5 MB source file with 94,628 entries, compiled into the browser. Its policy buckets record the requirements tightening over time, and whole TLDs like .dev and .app are in it.
  • Preloading retires SSL stripping for that domain. Every attack in that family needs one plain-HTTP request to answer, and a preloaded browser never makes one — so the port-80 redirect stops being a security control. It does not address an attacker holding a certificate the browser accepts; that is Certificate Transparency's problem, not HSTS's.
  • max-age=0 retracts a header-taught policy and does nothing to a preloaded one. They are different mechanisms wearing one header; removal from the list takes 6–12 weeks of release trains.
  • A persistent store that survives clearing cookies can hold an identifier. §14.9 predicted the supercookie; browsers answered by scoping the state.
  • Auto-upgrade is a default; HSTS is a commitment. Browser HTTPS-First weakens the case for preloading without making the declaration redundant, because a default remains overridable and HSTS does not.

Practice

Retrieval. Recover the property that separates HSTS from a redirect.
Discrimination. Two removals that look alike and take different amounts of time.
Transfer. Diagnose a failure that looks like DNS and is not.

References

  1. IETF. “RFC 6797: HTTP Strict Transport Security (HSTS).” RFC Editor, 2012. — the normative definition — receipt rules (§8.1), no click-through (§8.4, §12.1), the bootstrap gap (§14.6), the cookie rationale (§14.4), and the tracking note (§14.9)
  2. Google. “HSTS Preload List Submission.” hstspreload.org. — the submission requirements, and the removal process with its 6–12 week estimate
  3. Chromium. “transport_security_state_static.json.” Chromium source. — the list itself: the file that ships inside the browser, and the policy field this article counts
  4. WebKit. “Protecting Against HSTS Abuse.” WebKit Blog, 2018. — the supercookie technique described by the browser engineers who mitigated it
  5. Google. “HTTPS by default.” Google Security Blog, 2025. — the Chrome 154 / October 2026 schedule for Always Use Secure Connections
  6. MDN. “Strict-Transport-Security.” MDN. — the practical reference for the directives
  7. MDN. “upgrade-insecure-requests.” MDN. — why the CSP directive does not replace HSTS for third-party top-level navigation
  8. Where Did This Request Come From?.” — the companion piece on provenance headers, which presumes the TLS this one establishes

How to cite

APA
Mangalapilly, Y. J. (2026, August). HSTS: It's Time to Go HTTPS-Only. Saṃhitā Notes. https://yesudeep.com/blog/hsts-its-time-to-go-https-only/
BibTeX
@online{mangalapilly2026hsts,
  author  = {Yesudeep Jose Mangalapilly},
  title   = {HSTS: It's Time to Go HTTPS-Only},
  journal = {Sa\d{m}hit\=a Notes},
  year    = {2026},
  month   = {August},
  url     = {https://yesudeep.com/blog/hsts-its-time-to-go-https-only/},
  urldate = {2026-08-12},
}
Plain
Yesudeep Jose Mangalapilly. “HSTS: It's Time to Go HTTPS-Only.” Saṃhitā Notes, 2026. https://yesudeep.com/blog/hsts-its-time-to-go-https-only/.
RIS
TY  - ELEC
AU  - Mangalapilly, Yesudeep Jose
TI  - HSTS: It's Time to Go HTTPS-Only
T2  - Saṃhitā Notes
PY  - 2026
UR  - https://yesudeep.com/blog/hsts-its-time-to-go-https-only/
Y2  - 2026-08-12
ER  - 

Annotations

Thank you — your note is held for review and will appear once approved.

Thank you — your note is published.

Please sign in below to leave a note.