HSTS and Preloading
Tell the browser "never connect to this domain over HTTP, ever" -- and make it permanent
by embedding the directive in every browser's shipped preload list
When to Use
- Deploying HTTPS for a new domain and need to prevent HTTP downgrade attacks
- Configuring security headers for production web applications
- Evaluating whether to submit a domain to the HSTS preload list
- Auditing existing HSTS configuration for correctness
- Understanding SSL stripping attacks and how HSTS prevents them
Threat Context
SSL stripping attacks (first demonstrated by Moxie Marlinspike at Black Hat 2009)
intercept the initial HTTP request before the redirect to HTTPS and maintain a plaintext
connection with the victim while establishing HTTPS with the server. The attacker proxies
all traffic, rewriting HTTPS links to HTTP. The victim sees a working website with no
browser warning because the connection was never upgraded to HTTPS in the first place.
Tools like sslstrip automate this attack entirely. On public Wi-Fi networks, SSL stripping
is trivial to execute with ARP spoofing. HSTS eliminates this by telling the browser to
never attempt HTTP for the domain after the first successful HTTPS visit. The browser
internally rewrites any http:// URL to https:// before sending the request. HSTS
preloading eliminates even the first-visit vulnerability by shipping the directive in the
browser itself -- the browser knows to use HTTPS before it has ever visited the domain.
Instructions
Set the HSTS header on all HTTPS responses.
Strict-Transport-Security: max-age=31536000; includeSubDomains. The max-age=31536000
directive means the browser will remember the HSTS policy for 1 year (31,536,000
seconds). includeSubDomains applies the directive to all subdomains, not just the
apex domain. After receiving this header over a valid HTTPS connection, the browser
will refuse to connect to the domain over HTTP for the specified duration, automatically
upgrading any HTTP URL to HTTPS internally.
Redirect HTTP to HTTPS first. HSTS only works if the browser receives it over a
valid HTTPS connection. Set up an HTTP-to-HTTPS 301 (permanent) redirect on all HTTP
endpoints. The HSTS header must be sent on the HTTPS response -- it is ignored on HTTP
responses per RFC 6797. The redirect ensures users who type http:// are upgraded,
and the HSTS header ensures they stay upgraded.
Ramp up max-age gradually. Start with a short max-age (5 minutes = 300 seconds)
while testing. Verify no mixed content issues, no broken subdomains, no HTTP-only
internal tools that would be affected. Increase to 1 week (604800), then 1 month
(2592000), then 1 year (31536000). If HSTS is deployed with a 1-year max-age and
HTTPS breaks (certificate expired, misconfiguration), the domain is inaccessible for
up to 1 year for users who cached the directive. There is no way to revoke an HSTS
directive from the server side once cached.
Submit to the HSTS preload list. Once max-age is at least 1 year,
includeSubDomains is set, and a preload directive is added
(Strict-Transport-Security: max-age=31536000; includeSubDomains; preload), submit
the domain at hstspreload.org. Once accepted, every Chrome, Firefox, Safari, and Edge
browser ships with the directive baked in at build time. The first-visit vulnerability
is eliminated entirely. This is the strongest transport security guarantee available.
Ensure all subdomains support HTTPS before adding includeSubDomains. If any
subdomain serves HTTP-only content (legacy internal tools, staging environments,
development servers, marketing landing pages on a subdomain), includeSubDomains will
break it. Audit all subdomains -- including those managed by other teams, third-party
services using CNAME records, and forgotten legacy systems -- before enabling
includeSubDomains.
Understand preload list removal is slow. Removing a domain from the preload list
requires: removing the preload directive from the header, submitting a removal
request at hstspreload.org, waiting for browser vendors to process it (weeks to
months), and then waiting for all users to update their browsers. Full removal can take
6-12 months. This is intentional -- preloading is a commitment, not a toggle.
Details
SSL Stripping Attack Flow
- Victim connects to
http://bank.com (typed without https:// or clicked a link)
- Attacker on the same network intercepts the HTTP request (via ARP spoofing, rogue AP)
- Attacker connects to
https://bank.com on behalf of the victim
- Attacker proxies all traffic, rewriting
https:// links in responses to http://
- Victim sees a fully functional bank website served over HTTP
- Attacker captures all credentials, session tokens, and financial data in plaintext
The victim has no browser warning because the connection was HTTP from the start. HSTS
defeats this because the browser knows (from a previous visit or the preload list) that
bank.com must use HTTPS and refuses to send any HTTP request.
HSTS Preload List Mechanics
The preload list is maintained as a JSON file in the Chromium source code
(net/http/transport_security_state_static.json). Firefox, Safari, and Edge consume the
same list. Submission requirements: the preload directive in the HSTS header,
includeSubDomains directive, max-age of at least 31536000 (1 year), valid HTTPS on the
apex domain (not just www), and valid HTTPS on all subdomains. The list is checked at
browser build time and shipped with each browser release, not fetched at runtime.
Common Deployment Mistakes
- Mixed content: loading images, scripts, or stylesheets over HTTP from an HTTPS page
(browsers block or warn, degrading user experience)
- Redirect loops: HTTP redirects to HTTPS which redirects back to HTTP
- Subdomains without HTTPS:
internal.example.com only serves HTTP, but
includeSubDomains forces HTTPS
- HSTS header on HTTP responses: ignored by spec, but confusing during debugging
- Setting
max-age=0 accidentally: disables HSTS, removing protection
Testing HSTS Configuration
Before deploying HSTS to production, verify the configuration thoroughly. Use
curl -sI https://example.com | grep -i strict to check the header is present and
correctly formatted. Use the Qualys SSL Labs test (ssllabs.com/ssltest) which reports
HSTS status, max-age, and preload eligibility. Use hstspreload.org to check whether the
domain meets all preload requirements. Test from multiple clients and browsers: some
proxies strip security headers, and some CDN configurations do not forward them. Verify
that the header is not sent on HTTP responses (it should only appear on HTTPS). Test
subdomain coverage by checking HTTPS on every subdomain you can discover.
Interaction with Other Security Headers
HSTS works with Content-Security-Policy: upgrade-insecure-requests which automatically
rewrites HTTP sub-resource URLs to HTTPS within the page. Together they ensure that both
navigation and sub-resource loading use HTTPS. HSTS also interacts with
Referrer-Policy: HTTPS-to-HTTP transitions strip the referrer header, which HSTS
prevents by ensuring all connections are HTTPS. The combination of HSTS, CSP
upgrade-insecure-requests, and proper referrer policy creates a comprehensive transport
security posture.
Anti-Patterns
HSTS without testing subdomains. Enabling includeSubDomains when
legacy.example.com only serves HTTP breaks that subdomain for the HSTS max-age
duration. Users cannot bypass this -- the browser refuses to connect. Audit all
subdomains first, including those managed by other teams.
max-age of 0 in production. max-age=0 disables HSTS for any browser that
receives it. If an attacker can force the browser to receive this header (via MITM on
the first request before HSTS is cached), HSTS protection is removed for that user.
Never set max-age to 0 in production.
Preloading before readiness. Submitting to the preload list before all subdomains
support HTTPS, before the team understands the commitment, or before testing with a
long max-age. Preload removal takes months and requires browser updates to propagate.
Treat preloading as irreversible for practical purposes.
HSTS on HTTP responses. The Strict-Transport-Security header on an HTTP response
is ignored per RFC 6797 Section 7.2. If browsers honored it on HTTP, an attacker could
inject this header on an HTTP response to DoS a domain by forcing HTTPS on a domain
that does not support it. Browsers correctly ignore it on HTTP, but its presence
indicates a misconfiguration.
Relying only on HTTP-to-HTTPS redirects. Redirects alone do not prevent SSL
stripping. The first request is still HTTP and interceptable. The redirect itself
travels over HTTP and can be modified by the attacker. HSTS ensures subsequent requests
are HTTPS without relying on a redirect. Preloading ensures even the first request is
HTTPS.
1---2name: security-hsts-preloading3description: HSTS and Preloading4---5# HSTS and Preloading67> Tell the browser "never connect to this domain over HTTP, ever" -- and make it permanent8> by embedding the directive in every browser's shipped preload list910## When to Use1112- Deploying HTTPS for a new domain and need to prevent HTTP downgrade attacks13- Configuring security headers for production web applications14- Evaluating whether to submit a domain to the HSTS preload list15- Auditing existing HSTS configuration for correctness16- Understanding SSL stripping attacks and how HSTS prevents them1718## Threat Context1920SSL stripping attacks (first demonstrated by Moxie Marlinspike at Black Hat 2009)21intercept the initial HTTP request before the redirect to HTTPS and maintain a plaintext22connection with the victim while establishing HTTPS with the server. The attacker proxies23all traffic, rewriting HTTPS links to HTTP. The victim sees a working website with no24browser warning because the connection was never upgraded to HTTPS in the first place.25Tools like sslstrip automate this attack entirely. On public Wi-Fi networks, SSL stripping26is trivial to execute with ARP spoofing. HSTS eliminates this by telling the browser to27never attempt HTTP for the domain after the first successful HTTPS visit. The browser28internally rewrites any `http://` URL to `https://` before sending the request. HSTS29preloading eliminates even the first-visit vulnerability by shipping the directive in the30browser itself -- the browser knows to use HTTPS before it has ever visited the domain.3132## Instructions33341. **Set the HSTS header on all HTTPS responses.**35 `Strict-Transport-Security: max-age=31536000; includeSubDomains`. The `max-age=31536000`36 directive means the browser will remember the HSTS policy for 1 year (31,536,00037 seconds). `includeSubDomains` applies the directive to all subdomains, not just the38 apex domain. After receiving this header over a valid HTTPS connection, the browser39 will refuse to connect to the domain over HTTP for the specified duration, automatically40 upgrading any HTTP URL to HTTPS internally.41422. **Redirect HTTP to HTTPS first.** HSTS only works if the browser receives it over a43 valid HTTPS connection. Set up an HTTP-to-HTTPS 301 (permanent) redirect on all HTTP44 endpoints. The HSTS header must be sent on the HTTPS response -- it is ignored on HTTP45 responses per RFC 6797. The redirect ensures users who type `http://` are upgraded,46 and the HSTS header ensures they stay upgraded.47483. **Ramp up max-age gradually.** Start with a short max-age (5 minutes = 300 seconds)49 while testing. Verify no mixed content issues, no broken subdomains, no HTTP-only50 internal tools that would be affected. Increase to 1 week (604800), then 1 month51 (2592000), then 1 year (31536000). If HSTS is deployed with a 1-year max-age and52 HTTPS breaks (certificate expired, misconfiguration), the domain is inaccessible for53 up to 1 year for users who cached the directive. There is no way to revoke an HSTS54 directive from the server side once cached.55564. **Submit to the HSTS preload list.** Once max-age is at least 1 year,57 `includeSubDomains` is set, and a `preload` directive is added58 (`Strict-Transport-Security: max-age=31536000; includeSubDomains; preload`), submit59 the domain at hstspreload.org. Once accepted, every Chrome, Firefox, Safari, and Edge60 browser ships with the directive baked in at build time. The first-visit vulnerability61 is eliminated entirely. This is the strongest transport security guarantee available.62635. **Ensure all subdomains support HTTPS before adding `includeSubDomains`.** If any64 subdomain serves HTTP-only content (legacy internal tools, staging environments,65 development servers, marketing landing pages on a subdomain), `includeSubDomains` will66 break it. Audit all subdomains -- including those managed by other teams, third-party67 services using CNAME records, and forgotten legacy systems -- before enabling68 `includeSubDomains`.69706. **Understand preload list removal is slow.** Removing a domain from the preload list71 requires: removing the `preload` directive from the header, submitting a removal72 request at hstspreload.org, waiting for browser vendors to process it (weeks to73 months), and then waiting for all users to update their browsers. Full removal can take74 6-12 months. This is intentional -- preloading is a commitment, not a toggle.7576## Details7778### SSL Stripping Attack Flow79801. Victim connects to `http://bank.com` (typed without `https://` or clicked a link)812. Attacker on the same network intercepts the HTTP request (via ARP spoofing, rogue AP)823. Attacker connects to `https://bank.com` on behalf of the victim834. Attacker proxies all traffic, rewriting `https://` links in responses to `http://`845. Victim sees a fully functional bank website served over HTTP856. Attacker captures all credentials, session tokens, and financial data in plaintext8687The victim has no browser warning because the connection was HTTP from the start. HSTS88defeats this because the browser knows (from a previous visit or the preload list) that89bank.com must use HTTPS and refuses to send any HTTP request.9091### HSTS Preload List Mechanics9293The preload list is maintained as a JSON file in the Chromium source code94(`net/http/transport_security_state_static.json`). Firefox, Safari, and Edge consume the95same list. Submission requirements: the `preload` directive in the HSTS header,96`includeSubDomains` directive, max-age of at least 31536000 (1 year), valid HTTPS on the97apex domain (not just `www`), and valid HTTPS on all subdomains. The list is checked at98browser build time and shipped with each browser release, not fetched at runtime.99100### Common Deployment Mistakes101102- Mixed content: loading images, scripts, or stylesheets over HTTP from an HTTPS page103 (browsers block or warn, degrading user experience)104- Redirect loops: HTTP redirects to HTTPS which redirects back to HTTP105- Subdomains without HTTPS: `internal.example.com` only serves HTTP, but106 `includeSubDomains` forces HTTPS107- HSTS header on HTTP responses: ignored by spec, but confusing during debugging108- Setting `max-age=0` accidentally: disables HSTS, removing protection109110### Testing HSTS Configuration111112Before deploying HSTS to production, verify the configuration thoroughly. Use113`curl -sI https://example.com | grep -i strict` to check the header is present and114correctly formatted. Use the Qualys SSL Labs test (ssllabs.com/ssltest) which reports115HSTS status, max-age, and preload eligibility. Use hstspreload.org to check whether the116domain meets all preload requirements. Test from multiple clients and browsers: some117proxies strip security headers, and some CDN configurations do not forward them. Verify118that the header is not sent on HTTP responses (it should only appear on HTTPS). Test119subdomain coverage by checking HTTPS on every subdomain you can discover.120121### Interaction with Other Security Headers122123HSTS works with `Content-Security-Policy: upgrade-insecure-requests` which automatically124rewrites HTTP sub-resource URLs to HTTPS within the page. Together they ensure that both125navigation and sub-resource loading use HTTPS. HSTS also interacts with126`Referrer-Policy`: HTTPS-to-HTTP transitions strip the referrer header, which HSTS127prevents by ensuring all connections are HTTPS. The combination of HSTS, CSP128upgrade-insecure-requests, and proper referrer policy creates a comprehensive transport129security posture.130131## Anti-Patterns1321331. **HSTS without testing subdomains.** Enabling `includeSubDomains` when134 `legacy.example.com` only serves HTTP breaks that subdomain for the HSTS max-age135 duration. Users cannot bypass this -- the browser refuses to connect. Audit all136 subdomains first, including those managed by other teams.1371382. **max-age of 0 in production.** `max-age=0` disables HSTS for any browser that139 receives it. If an attacker can force the browser to receive this header (via MITM on140 the first request before HSTS is cached), HSTS protection is removed for that user.141 Never set max-age to 0 in production.1421433. **Preloading before readiness.** Submitting to the preload list before all subdomains144 support HTTPS, before the team understands the commitment, or before testing with a145 long max-age. Preload removal takes months and requires browser updates to propagate.146 Treat preloading as irreversible for practical purposes.1471484. **HSTS on HTTP responses.** The `Strict-Transport-Security` header on an HTTP response149 is ignored per RFC 6797 Section 7.2. If browsers honored it on HTTP, an attacker could150 inject this header on an HTTP response to DoS a domain by forcing HTTPS on a domain151 that does not support it. Browsers correctly ignore it on HTTP, but its presence152 indicates a misconfiguration.1531545. **Relying only on HTTP-to-HTTPS redirects.** Redirects alone do not prevent SSL155 stripping. The first request is still HTTP and interceptable. The redirect itself156 travels over HTTP and can be modified by the attacker. HSTS ensures subsequent requests157 are HTTPS without relying on a redirect. Preloading ensures even the first request is158 HTTPS.