HSTS and the HSTS preload list
How Strict-Transport-Security works, how to raise max-age safely, what includeSubDomains can break, and the requirements and risks of preloading.
Why HTTPS alone is not enough
Most visitors reach a site by typing a name or following an old link that starts with http://. The server then redirects to HTTPS, but that first plain request can be intercepted on a hostile network. An attacker in the middle can keep the victim on HTTP and proxy the real site, a technique known as SSL stripping.
HTTP Strict Transport Security (RFC 6797) closes that gap for returning visitors. After a browser has seen the Strict-Transport-Security header over a valid HTTPS connection, it rewrites every future request for that host to HTTPS internally, before anything goes on the network. It also refuses to let the user click through certificate errors for that host.
The header
Strict-Transport-Security: max-age=31536000; includeSubDomains| Directive | Meaning |
|---|---|
max-age=<seconds> | How long the browser remembers the policy, renewed on every response that carries the header. 0 removes the policy. |
includeSubDomains | Applies the policy to every subdomain of the host as well. |
preload | Not part of RFC 6797; signals consent to inclusion in browser preload lists. |
Browsers ignore the header when it arrives over plain HTTP, because an attacker could inject it there. Send it on HTTPS responses only, and on every response, including redirects and error pages, so the policy is refreshed whatever page a visitor hits.
server {
listen 443 ssl;
server_name example.com;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
}
server {
listen 80;
server_name example.com;
return 301 https://example.com$request_uri;
}<VirtualHost *:443>
ServerName example.com
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"
</VirtualHost>The always keyword matters
add_header without always is skipped on error responses such as 404 or 500, and a header set in a location block replaces headers set at the server level. Check real responses, not just the configuration.Rolling out HSTS safely
The risk with HSTS is not the header itself but its memory. If you set a year-long policy and later need plain HTTP on that host, or a certificate lapses, browsers that saw the header will refuse to connect until the policy expires. A staged rollout finds those problems while max-age is still short.
| Stage | Header | Wait before next stage |
|---|---|---|
| 1. Test | max-age=300 | A day; check the site and key flows |
| 2. Short | max-age=86400 | A week; watch errors and support tickets |
| 3. Subdomains | max-age=86400; includeSubDomains | A week, after checking every subdomain |
| 4. Month | max-age=2592000; includeSubDomains | A month |
| 5. Year | max-age=31536000; includeSubDomains | Long term; consider preload |
Before step 3, list every subdomain that exists in DNS and check that each one works over HTTPS with a valid certificate. The DNS Lookup and your DNS zone export are the starting point; do not forget printers, old intranet names and staging hosts that only people inside the company use.
Communicate each stage to the teams that run subdomains. A short note with the date and the planned max-age gives them a chance to raise an HTTP-only service before it breaks.
Check the header on your real responses with the HTTP Headers Checker. It reports the max-age in days and whether includeSubDomains and preload are present.
What includeSubDomains can break
includeSubDomains set on example.com applies to every name under it, including ones served by other teams or vendors. Any subdomain that must still be reached over plain HTTP becomes unreachable for browsers that have the policy. Typical victims are internal tools, network devices with self-signed certificates and legacy marketing microsites.
- Internal hosts such as
intranet.example.comserved over HTTP inside the office network. - Devices with self-signed certificates, because HSTS removes the click-through option.
- Vendor-hosted subdomains (help centres, status pages) where you do not control the certificate.
- Captive portals or HTTP-only APIs on subdomains used by old clients.
The header is only honoured for the host that sent it and its subdomains. A policy with includeSubDomains sent by www.example.com covers *.www.example.com, not api.example.com. To cover the whole domain, the header must be sent by example.com itself, which means users need to visit the bare domain at least once, or the domain must be preloaded.
The preload list
HSTS protects a browser only after its first successful HTTPS visit. The preload list removes that first-visit gap: it is a list of domains compiled into Chromium and also used by Firefox, Safari and Edge, which treat those domains as HSTS from the start. Domains are submitted through hstspreload.org, which checks the requirements automatically.
| Requirement | What it means in practice |
|---|---|
| Valid certificate | The bare domain serves HTTPS with a trusted certificate |
| Redirect HTTP to HTTPS | If port 80 is open, http://example.com redirects to HTTPS on the same host first |
| All subdomains on HTTPS | Including www if it exists in DNS |
| HSTS header on the bare domain | max-age of at least 31536000, plus includeSubDomains and preload |
| Header on redirects too | An HTTPS redirect from the bare domain must still send the header |
Strict-Transport-Security: max-age=63072000; includeSubDomains; preloadRemoval is slow
Some top-level domains are preloaded as a whole, so every domain registered under them requires HTTPS in browsers from the first visit. If you use such a TLD, HSTS behaviour is already in place regardless of your header, and plain HTTP subdomains will not work.
Redirects that work with HSTS
A clean redirect chain matters for both HSTS and preloading. The recommended sequence is: http://example.com → https://example.com (setting HSTS for the bare domain) → https://www.example.com if you use www. Redirecting straight from http://example.com to https://www.example.com skips the bare domain's HTTPS response, so it never sends its policy.
http://example.com/ 301 → https://example.com/
https://example.com/ 301 → https://www.example.com/ (with Strict-Transport-Security)
http://www.example.com/ 301 → https://www.example.com/
https://www.example.com/ 200 (with Strict-Transport-Security)The Redirect Checker follows the chain from http:// and lists every hop, which makes it easy to confirm the first hop stays on the same host. The redirect guide covers loops and unnecessary hops.
Common mistakes
| Mistake | Effect | Fix |
|---|---|---|
| Header only on the home page | Visitors who land elsewhere never get the policy | Send it on every HTTPS response |
| Header missing on 301 or error responses | Redirect-only hosts never set the policy | Use always in nginx or Header always set in Apache |
Year-long max-age on day one | Mistakes stay cached in browsers for a year | Raise max-age in stages |
includeSubDomains on www only | Bare domain and other subdomains are not covered | Send the header from the bare domain |
preload added without submitting | No effect, but signals consent to preloading | Remove it unless you intend to preload |
| Two different headers from app and proxy | Browsers use the first valid one | Set the header in one layer only |
The last row is common behind CDNs and reverse proxies: the application sends one value and the edge adds another. Decide which layer owns security headers and remove the other, then confirm the final response from outside with the HTTP Headers Checker.
HSTS and related mechanisms
Several newer features also push browsers toward HTTPS, and it helps to know how they relate. None of them replaces HSTS for a site you control, but they change what visitors experience on sites without it.
| Mechanism | Set by | Scope |
|---|---|---|
| HSTS header (RFC 6797) | Your server | Your host, optionally subdomains, remembered for max-age |
| HSTS preload list | Browser vendors, on your request | Your domain and all subdomains, before the first visit |
| HTTPS DNS records (RFC 9460) | Your DNS | Browsers that support them can connect over HTTPS directly |
| HTTPS-first or HTTPS-only browser modes | The user or browser | All sites, with a warning when HTTPS fails |
upgrade-insecure-requests (CSP) | Your server | Subresources inside your pages, not navigation to your site |
HSTS remains the mechanism you control that makes certificate errors non-bypassable and protects returning visitors on every browser that implements it. The others are useful additions, and upgrade-insecure-requests in particular pairs well with HSTS during clean-up of old content.
Living with HSTS
HSTS turns certificate problems into outages. Automate renewal, monitor expiry separately, and test renewals on staging hosts. A lapsed certificate on a site without HSTS shows a warning; on a site with HSTS, visitors simply cannot get in.
- Monitor certificate expiry for every host covered by the policy, including subdomains.
- Add new subdomains only with HTTPS from day one.
- Keep the header on every HTTPS response, including CDN edge responses.
- Document that the domain is HSTS or preloaded, so future teams do not plan HTTP-only services on it.
- Pair HSTS with the other security headers; the CSP guide covers the most complex one.
Large organisations often have many domains, and HSTS decisions differ per domain. A product domain with a single web application is an easy candidate for a long max-age and preloading; a corporate domain with dozens of internal subdomains may only be ready for HSTS on specific hosts without includeSubDomains. Record the decision and its reasons per domain, so the next team knows what is safe.
When you acquire or inherit a domain, check whether it is already preloaded or sends HSTS before planning services on it. The HTTP Headers Checker shows the current header, and the preload site shows the list status. Discovering a preloaded domain after building an HTTP-only service on one of its subdomains is an avoidable surprise.
If you ever have to back out, send max-age=0 over HTTPS. Browsers that receive it forget the policy for that host. Visitors who do not return before their stored max-age expires keep the old policy until then, which is another reason to raise max-age gradually.
FAQ
What max-age should I use?
Start with minutes or a day, then raise it in steps. For a stable HTTPS-only site, one year (31536000) or two years (63072000) is common, and at least one year is required for preloading.
Does HSTS work on the first visit?
Not unless the domain is on the preload list. Otherwise the browser learns the policy from the first successful HTTPS response.
Can I set HSTS in a meta tag?
No. RFC 6797 requires the header; browsers ignore HSTS in HTML meta elements.
Should I send HSTS on HTTP responses?
It has no effect there, because browsers ignore it over plain HTTP. Redirect HTTP to HTTPS and send the header on HTTPS responses.
How do I remove HSTS?
Send max-age=0 over HTTPS and, if preloaded, request removal at hstspreload.org. Returning visitors drop the policy when they see the new header; preload removal waits for browser updates.
Why can I not click through a certificate warning on my own site?
Your browser has the HSTS policy for that host, so certificate errors are treated as fatal. Fix the certificate; for local development, use a different host name that is not covered by the policy.
Does HSTS affect APIs and mobile apps?
Only clients that implement HSTS, which mainly means browsers. API clients and apps should use HTTPS URLs directly and validate certificates; HSTS adds nothing for them.
Can includeSubDomains break email?
No. HSTS applies to HTTP requests in browsers. Mail servers, SMTP and IMAP connections are not affected, even for subdomains covered by the policy.
Should staging sites send HSTS?
Use a short max-age on staging, and keep staging on a host name that is not covered by production's includeSubDomains if it ever needs plain HTTP or test certificates.
Does HSTS replace redirects from HTTP to HTTPS?
No. First-time visitors and clients that ignore HSTS still need the redirect. HSTS protects the requests after that.