Protocol
HSTS: Strict-Transport-Security, preload and common pitfalls
HSTS (HTTP Strict Transport Security) is a response header that tells browsers to reach a host only over HTTPS for a set period. After seeing Strict-Transport-Security: max-age=31536000, a browser rewrites every http:// request for that host to https:// locally and blocks click-through on certificate errors. It closes the gap a 301 redirect leaves open, but makes certificate mistakes fatal.
What problem HSTS solves
HSTS removes the insecure first hop that a plain redirect leaves behind. Without it, a user who types example.com or follows an old http:// link sends the first request in cleartext. Anyone on the path (public Wi-Fi, a compromised router) can answer that request, keep the user on HTTP and proxy the real HTTPS site behind the scenes. This is the "SSL stripping" attack that motivated RFC 6797.
Without HSTS With HSTS (after first visit)
------------ ------------------------------
User types example.com User types example.com
| |
v v
GET http://example.com/ (cleartext) Browser rewrites internally:
| <-- attacker can intercept here https://example.com/ (307 internal)
v |
301 -> https://example.com/ v
| TLS handshake, certificate checked
v (no click-through on errors)
TLS handshake
The redirect still matters, because it is how first-time visitors reach HTTPS. HSTS is what makes every later visit skip HTTP entirely.
The header and its three directives
The header has one required directive and two optional ones, and the browser only accepts it over a valid HTTPS connection.
Strict-Transport-Security: max-age=63072000; includeSubDomains; preload
| Directive | Defined in | Meaning |
|---|---|---|
max-age=<seconds> |
RFC 6797 | Required. How long the browser remembers the policy. Each new response resets the timer. max-age=0 deletes the policy. |
includeSubDomains |
RFC 6797 | Applies the policy to every subdomain at any depth: www, api, intranet.corp, and so on. |
preload |
Not in RFC 6797 | A signal to hstspreload.org that the owner consents to being added to the browsers' built-in list. Browsers ignore it as a directive. |
Rules from RFC 6797 that trip people up:
- HTTP responses are ignored. A header on a plain HTTP response has no effect. This prevents an attacker from setting or clearing your policy.
- The certificate must be valid. If the HTTPS response itself had certificate errors, the header is not recorded.
- The policy is per host. A header from
www.example.comdoes not coverexample.com. To cover the apex withincludeSubDomains, the apex itself must send the header, which is why preload requirements check the base domain. - Errors become hard failures. Section 12.1 of RFC 6797 calls this "no user recourse". On an HSTS host, an expired certificate, a name mismatch or an unknown issuer produces an error page with no "proceed" link.
The first-visit problem and the preload list
HSTS cannot protect a browser that has never seen your header, and the preload list exists to close that gap. Chrome ships a hard-coded list of HSTS hosts in its source code; Firefox, Safari and Edge build their lists from the Chrome list. Domains on the list are HTTPS-only from the first request ever made.
To be accepted through hstspreload.org, a domain must (as of November 2026):
- Serve a valid certificate.
- Redirect from HTTP to HTTPS on the same host if it listens on port 80.
- Serve all subdomains over HTTPS, including
wwwif a DNS record exists for it. Internal subdomains that are not publicly reachable count too. - Send an HSTS header on the base domain over HTTPS with
max-ageof at least31536000seconds,includeSubDomainsandpreload. - Keep the HSTS header on any additional redirect served from the HTTPS site.
The site's own wording is blunt: removal "tends to be slow and painful", changes "can take several months" to reach stable Chrome, and other browsers are not guaranteed to follow. The current page also states that HSTS is recommended but HSTS preloading is not, because Chrome and Safari already upgrade most HTTP navigations to HTTPS automatically, so preloading only helps against an active attacker who blocks that upgrade.
Whole top-level domains can be preloaded as well. Google preloaded .dev and .app (among other TLDs it operates), so every domain registered under them is HTTPS-only in browsers before it is even set up. If you buy an .app domain, plan for a certificate from day one; there is no HTTP fallback.
Rolling out HSTS safely
Ramp max-age up in stages and wait out each stage before moving to the next. hstspreload.org recommends exactly this, with includeSubDomains from the start so that subdomain breakage shows up early.
| Stage | Header value | Wait before next stage | What to check |
|---|---|---|---|
| 1 | max-age=300; includeSubDomains |
5 minutes to a day | Every subdomain loads over HTTPS; no mixed content |
| 2 | max-age=604800; includeSubDomains |
1 week | Internal tools, staging hosts, mail webmail, old marketing sites |
| 3 | max-age=2592000; includeSubDomains |
1 month | Traffic and error metrics stay flat |
| 4 | max-age=31536000; includeSubDomains |
Ongoing | Certificate monitoring and automated renewal in place |
| 5 (optional) | max-age=63072000; includeSubDomains; preload |
Then submit to hstspreload.org | You are sure no subdomain will ever need HTTP |
The waiting matters because a browser that saw a long max-age keeps it even if you shorten the value later; it only learns the new value on its next visit.
Before you start, make sure certificate renewal is automated. Under HSTS a missed renewal is an outage, not a warning users can click past. See Automating certificate renewal and the shrinking lifetimes described in 47-day certificates.
Server configuration examples
Send the header on every HTTPS response, including error pages and redirects. In nginx that means the always parameter; without it, add_header is skipped for 4xx and 5xx responses.
server {
listen 80;
server_name example.com www.example.com;
return 301 https://$host$request_uri; # no HSTS header here, it would be ignored
}
server {
listen 443 ssl;
http2 on;
server_name example.com www.example.com;
ssl_certificate /etc/ssl/example.com/fullchain.pem;
ssl_certificate_key /etc/ssl/example.com/privkey.pem;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
location / {
root /var/www/example.com;
}
}
One nginx detail causes many "HSTS disappeared" reports: add_header directives are inherited from the enclosing level only if the current level defines no add_header of its own. If a location block adds Cache-Control with add_header, it silently drops the HSTS header for that location. Repeat the HSTS line in that block or move shared headers into an include file.
Apache (with mod_headers enabled):
<VirtualHost *:443>
ServerName example.com
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"
</VirtualHost>
Behind a CDN or load balancer, set the header where TLS terminates for browsers, which is usually the CDN. Most CDN dashboards have an HSTS switch in their HTTPS settings. If both the CDN and the origin add the header you may send it twice; browsers use the first one, so keep the values identical or set it in only one place.
Verify the result from the command line:
curl -sI https://example.com/ | grep -i strict-transport-security
curl -sI https://example.com/does-not-exist | grep -i strict-transport-security # 404s too
strict-transport-security: max-age=31536000; includeSubDomains
The HSTS checker runs the same checks and also reports whether the domain is on the preload list.
Common pitfalls
Most HSTS incidents come from subdomains nobody remembered, not from the main site.
- Forgotten HTTP-only subdomains.
includeSubDomainscoversprinter.office.example.com, an oldblogon shared hosting, or a vendor-runstatuspage. If any of them lacks a valid certificate, browsers that visited the apex can no longer open it. - Internal names under a public domain. Intranet hosts such as
git.corp.example.comwith self-signed or private-CA certificates break for staff browsers once the apex sendsincludeSubDomains, unless the private CA is installed on every device. - Development on a real domain. Developers who visit
dev.example.com:8080over HTTP after the policy lands get forced to HTTPS. Use a separate domain for local development. - Clearing HSTS in one browser is not a fix. Chrome lets a user delete a dynamic entry at
chrome://net-internals/#hsts, but preloaded entries cannot be deleted there, and your users will not do it anyway. - Header only on the home page. Frameworks that set headers in application code often miss static files and error pages. Set it at the web server or proxy.
- Expired certificate equals outage. This is the price of "no user recourse". Monitor expiry and the full chain; see certificate chains.
HSTS, HTTPS DNS records and HTTPS-by-default browsers
HSTS is no longer the only thing pushing browsers to HTTPS, but it is still the only one that also forbids click-through. RFC 9460 (November 2023) defines the HTTPS DNS record; section 9.5 says a client that finds an HTTPS record for a host should treat an http:// request as a 307 redirect to https://, "similar to HSTS". It also allows clients to remove the user's option to bypass certificate errors for such origins, and says origins publishing the record must not rely on that bypass.
Major browsers also try HTTPS first on their own: hstspreload.org notes that Chrome and Safari automatically upgrade HTTP navigations to HTTPS regardless of HSTS. Those upgrades fall back to HTTP when HTTPS fails, which is exactly what an attacker exploits. HSTS has no fallback.
| Mechanism | Where the signal lives | Protects first visit | Falls back to HTTP | Blocks click-through |
|---|---|---|---|---|
| 301 redirect | HTTP response | No | Yes (attacker can strip) | No |
| Browser HTTPS upgrade | Browser feature | Yes, best effort | Yes | No |
| HTTPS DNS record (RFC 9460) | DNS | Yes, if DNS is trusted | Depends on client | Optional (MAY) |
| HSTS header (RFC 6797) | HTTPS response | No | No | Yes |
| HSTS preload | Browser source code | Yes | No | Yes |
A practical checklist
Do not raise max-age above one month until every item below is true:
- Every DNS name under the domain resolves to something that serves a valid certificate, or does not resolve at all. A CT search of your domain lists names that have had certificates, which is a good inventory starting point.
- Certificate renewal is automated and monitored, with alerts well before expiry.
- No page loads mixed content; HSTS upgrades navigations to your host but does not fix hard-coded
http://URLs on third-party hosts. - The header is present on 200, 301, 404 and 500 responses.
- You understand that preloading is effectively one-way for months, and that hstspreload.org itself no longer recommends it for most sites.
For the protocol underneath, read How TLS works; for the glossary entries, see HSTS and HSTS preload.
FAQ
What is a good HSTS max-age value?
Start with max-age=300 and raise it in stages (one week, one month) while you watch for broken subdomains. The long-term value most sites use is one or two years: max-age=31536000 (365 days) or max-age=63072000 (730 days). One year is also the minimum hstspreload.org accepts.
Does sending HSTS over HTTP do anything?
No. RFC 6797 says browsers must ignore the Strict-Transport-Security header on responses delivered over plain HTTP, because an attacker could inject or strip it. Redirect HTTP to HTTPS first, then send the header on the HTTPS response.
How do I turn HSTS off?
Send Strict-Transport-Security: max-age=0 over HTTPS. Each browser forgets the policy the next time it visits. Browsers that never come back keep the old policy until the original max-age runs out. If the domain is preloaded, you also have to request removal at hstspreload.org and wait for browser releases, which takes months.
Can users bypass a certificate warning on an HSTS site?
No. For a known HSTS host the browser must treat any certificate or TLS error as fatal with no 'proceed anyway' option. That is why HSTS sites need certificate monitoring: an expired certificate takes the site fully offline for returning visitors.
Should I submit my domain to the HSTS preload list?
Only if every subdomain, including internal ones, will serve valid HTTPS for the long term. hstspreload.org now states that HSTS is recommended but preloading is not, because Chrome and Safari already upgrade most navigations to HTTPS and removal from the list is slow.