Privacy & Security

How to set up HSTS without locking yourself out

A staged, reversible rollout plan for Strict-Transport-Security that improves privacy without turning one bad certificate into an outage.

The Wux Webtools Team The Wux Webtools Team 8 min read AI-assisted, human-reviewed
Illustration of a secure browser connection with redirect arrows and subdomain warning markers.
Table of contents
  1. HSTS is simple until it is not
  2. What the HSTS header actually does
  3. The lockout scenarios to avoid
  4. 1. A forgotten subdomain is not HTTPS-ready
  5. 2. A certificate expires
  6. 3. Staging or internal tools live under the production domain
  7. 4. Preload is treated as a routine checkbox
  8. A safe rollout plan
  9. Step 1: Audit every hostname you control
  10. Step 2: Fix HTTPS before adding HSTS
  11. Step 3: Start with a very short max-age
  12. Step 4: Increase gradually
  13. Step 5: Add includeSubDomains only after the audit is real
  14. Step 6: Treat preload as a separate project
  15. Configuration examples
  16. Nginx
  17. Apache
  18. CDN or edge platform
  19. How to undo HSTS safely
  20. Testing checklist before you ship
  21. The privacy case for HSTS

HSTS is simple until it is not

HTTP Strict Transport Security, usually shortened to HSTS, tells browsers: “for this site, always use HTTPS.” Once a browser receives the header over a valid HTTPS connection, it remembers the rule for the duration you specify.

That is useful. It prevents protocol downgrade attacks, reduces accidental insecure requests, and avoids the awkward moment where a user types example.com and briefly touches plain HTTP before being redirected.

It is also sticky. If you publish the wrong HSTS policy, browsers may keep enforcing it long after you remove the header from your server. That is how teams lock themselves out: not from their own admin panel exactly, but from users’ browsers, subdomains, staging systems, legacy endpoints, and forgotten services that are not ready for forced HTTPS.

The goal is not to avoid HSTS. The goal is to deploy it like a migration, not like a toggle.

What the HSTS header actually does

A typical HSTS header looks like this:

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

It has three important parts:

  • max-age: how long, in seconds, the browser should enforce HTTPS for this host.
  • includeSubDomains: whether the rule also applies to every subdomain.
  • preload: a signal that you want the domain included in browser preload lists.

The browser only trusts this header when it receives it over valid HTTPS. If the certificate is invalid, expired, or mismatched, the browser should not accept a new HSTS policy from that response.

Once the policy is stored, future attempts to visit http://example.com are upgraded by the browser to https://example.com before the request is sent. That is the privacy win: the insecure request never leaves the device.

The lockout scenarios to avoid

Most HSTS failures are not caused by the main website. They happen at the edges.

1. A forgotten subdomain is not HTTPS-ready

includeSubDomains sounds tidy, but it is absolute. If you set it on example.com, it applies to:

  • www.example.com
  • api.example.com
  • old-crm.example.com
  • printer-setup.example.com
  • staging.example.com
  • anything else under that domain

If any of those hosts cannot serve valid HTTPS, users with the HSTS policy cached will be unable to reach them over HTTP.

2. A certificate expires

Without HSTS, users sometimes click through certificate warnings. That is not good security practice, but it happens.

With HSTS, modern browsers do not allow easy bypass of certificate errors for that host. This is the point. It also means certificate renewal needs to be boring, monitored, and tested.

3. Staging or internal tools live under the production domain

Putting internal tools under *.example.com can become painful once the parent domain uses includeSubDomains. If those tools use self-signed certificates, private certificate authorities, old TLS configurations, or no HTTPS at all, HSTS will expose the shortcut.

This is one reason many teams keep internal and experimental systems under a separate domain that has its own security policy.

4. Preload is treated as a routine checkbox

HSTS preload is not just another directive. It means your domain can be shipped inside browsers as HTTPS-only before any user has visited your site.

That closes the “first visit” gap, but it is much harder to undo. Removal from preload lists can take weeks or months to reach users, depending on browser release cycles. Preload is appropriate for stable, mature domains. It is not appropriate for a site that is still discovering its subdomain inventory.

A safe rollout plan

Step 1: Audit every hostname you control

Before setting includeSubDomains, list every hostname under the domain. DNS records are a start, but not the whole story. Check CDN configurations, hosting dashboards, email-related hostnames, old marketing tools, storage buckets, and internal documentation.

For each hostname, answer:

  • Does it serve HTTP, HTTPS, or both?
  • Is the HTTPS certificate valid and automatically renewed?
  • Does it redirect HTTP to HTTPS cleanly?
  • Is it meant to be public?
  • Is it still needed?

If your team already has production header debugging habits, this fits naturally beside redirect and header checks. We covered that workflow in a small toolkit for debugging redirects and HTTP headers in production.

Step 2: Fix HTTPS before adding HSTS

HSTS does not make a broken HTTPS setup secure. It only makes HTTPS mandatory.

Before enabling it, verify:

  • TLS certificates cover the correct hostnames.
  • Certificates renew automatically.
  • HTTP redirects to HTTPS with a single clean hop where possible.
  • Canonical host redirects are consistent, for example non-www to www, or the reverse.
  • Application assets do not depend on insecure http:// URLs.

Mixed content is less common than it used to be, but it still appears in old CMS themes, analytics snippets, embedded media, and hard-coded image paths.

Step 3: Start with a very short max-age

Do not begin with one year. Begin with five minutes:

Strict-Transport-Security: max-age=300

Deploy that only on the hostname you are testing, usually the canonical production website. Leave out includeSubDomains for now.

Then test in real browsers and with command-line requests:

curl -I https://example.com

You should see exactly one Strict-Transport-Security header. Duplicate HSTS headers from an app server and a CDN are a common source of confusion. Browsers generally apply the effective policy, but humans debugging an incident do not need ambiguity.

Step 4: Increase gradually

If nothing breaks, increase the duration in stages:

Strict-Transport-Security: max-age=86400

Then:

Strict-Transport-Security: max-age=604800

Then perhaps:

Strict-Transport-Security: max-age=2592000

A practical schedule is:

  • 5 minutes
  • 1 day
  • 1 week
  • 1 month
  • 6 months or 1 year

There is no prize for rushing. The whole point of staged rollout is to give your monitoring, support inbox, and edge cases time to tell you what your checklist missed.

Step 5: Add includeSubDomains only after the audit is real

Once every public subdomain is HTTPS-ready, you can consider:

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

This is the moment to be conservative. If one legacy service still needs HTTP, do not add includeSubDomains to the parent domain. Either migrate that service, move it to another domain, or accept that your HSTS policy must stay narrower for now.

Security headers should reflect reality. They should not be used as motivational posters for infrastructure you hope to have later.

Step 6: Treat preload as a separate project

Only consider preload when all of the following are true:

  • The domain and all subdomains support valid HTTPS.
  • HTTP redirects to HTTPS.
  • The HSTS header uses max-age of at least 31536000 seconds.
  • The header includes includeSubDomains.
  • The header includes preload.
  • You are confident you will not need plain HTTP anywhere under the domain.

A preload-ready header looks like this:

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

Submitting to the preload list is a long-term commitment. If the site is a campaign microsite, a temporary product domain, or a domain with unclear ownership boundaries, skip it.

Configuration examples

Nginx

Use always so the header is sent on error responses as well:

add_header Strict-Transport-Security "max-age=300" always;

After the rollout is stable:

add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;

Apache

With mod_headers enabled:

Header always set Strict-Transport-Security "max-age=300"

Later:

Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"

CDN or edge platform

If your CDN sets response headers, prefer managing HSTS in one place. Do not set one policy at the origin and another at the edge unless you have a very clear reason.

Also check whether the CDN applies headers to redirects, cached errors, and custom error pages. A production site is not only its 200 OK response.

How to undo HSTS safely

If you need to disable HSTS, send:

Strict-Transport-Security: max-age=0

But there is a catch: the browser must successfully reach the site over valid HTTPS to receive that header. If HTTPS itself is broken, users with a cached HSTS policy cannot fetch the instruction that would clear it.

So the usual recovery order is:

  1. Restore valid HTTPS.
  2. Serve Strict-Transport-Security: max-age=0.
  3. Keep it in place long enough for returning users to receive it.
  4. Remove or replace the header after the incident is resolved.

If the domain is preloaded, serving max-age=0 is not enough for new browser profiles. You also need to request removal from the preload list and wait for that change to ship through browser updates.

Testing checklist before you ship

Use this checklist before increasing max-age or adding includeSubDomains:

  • The canonical HTTPS URL returns a valid certificate.
  • HTTP redirects to HTTPS.
  • There is only one HSTS header.
  • The header appears on redirects and error responses where appropriate.
  • All public subdomains have valid HTTPS.
  • Certificate renewal is monitored.
  • No critical internal system depends on HTTP under the same parent domain.
  • Preload has been discussed explicitly, not added by habit.

Lighthouse may also flag missing or weak security headers in some contexts, but it should not be your only verification method. If you use it as part of a wider review, read the findings as signals rather than verdicts; the same mindset applies when you read a Lighthouse report without panicking.

<!-- tool-cta:start -->

💡 Try this: Before and after each HSTS change, inspect the Strict-Transport-Security response with Get Headers to confirm max-age, includeSubDomains and preload are what you expect.

<!-- tool-cta:end -->

The privacy case for HSTS

HSTS is often framed as a security header, and it is. It also has a privacy benefit: it reduces the chance that a user’s first request leaks over plain HTTP on an untrusted network.

That matters on airport Wi-Fi, hotel networks, corporate guest networks, and anywhere a user’s traffic might be observed or modified. A plain HTTP request can expose the hostname, path, cookies without the Secure flag, and other request details. HTTPS is not magic, but forcing it consistently removes an entire class of avoidable leakage.

The best HSTS deployments are uninteresting. They are rolled out slowly, backed by reliable certificates, and boring enough that nobody notices. That is exactly what you want from a header whose failure mode can be dramatic.

Frequently asked questions

What is a safe first HSTS header?
Start with `Strict-Transport-Security: max-age=300`. That gives browsers a five-minute policy, which is long enough to test behavior but short enough to recover from most mistakes quickly.
Should every site use includeSubDomains?
No. Use `includeSubDomains` only when every subdomain under the parent domain supports valid HTTPS and will continue to do so. One forgotten legacy host can become unreachable for users with the policy cached.
Is HSTS preload necessary?
Not for most small or medium sites. Preload protects the very first visit, but it is difficult to reverse and requires the whole domain namespace to be HTTPS-ready. Consider it only after a stable HSTS rollout.
Can I remove HSTS by deleting the header?
Deleting the header stops new policies from being set, but it does not clear policies already cached by browsers. To clear HSTS, serve `Strict-Transport-Security: max-age=0` over valid HTTPS.
Does HSTS fix mixed content?
No. HSTS forces the top-level site connection to HTTPS. You still need to fix insecure asset URLs, embedded content, and old hard-coded `http://` references separately.

Sources & further reading

  1. MDN Web Docs: Strict-Transport-Security
  2. OWASP HTTP Strict Transport Security Cheat Sheet
  3. HSTS Preload Submission Requirements
  4. RFC 6797: HTTP Strict Transport Security
About the author
The Wux Webtools Team

Last updated:

Keep reading