how to set HSTS without locking yourself out
A skill for enabling HTTP Strict Transport Security safely: verifying full HTTPS coverage first, rolling out with a short max-age, then raising it and considering preload. Use when adding the Strict-Transport-Security header to a site. Triggers: 'HSTS setup', 'Strict-Transport-Security header', 'HSTS preload'. Not for: general HTTPS migration, other security headers.
how to set HSTS without locking yourself out
TL;DR
Get every page and subdomain serving clean HTTPS first, then add the HSTS header with a tiny max-age and watch for breakage. Only raise max-age to a year and add subdomains or preload once the short window proves clean. HSTS mistakes are cached by browsers, so the rollout order is the whole game.
how to set HSTS without locking yourself outUse this when
- You are adding the Strict-Transport-Security header to a web service
- A security review flagged the missing HSTS header
- You are considering HSTS preload submission
- You inherited a site with HSTS and need to understand the safe change process
Not for this skill when
- The site does not fully work over HTTPS yet (fix that first, HSTS comes after)
- You need the full security-headers list (different skill)
- You are debugging a certificate problem (HSTS will make cert problems worse, not better)
Steps
1. Prove everything works over HTTPS
Crawl the site and every subdomain over HTTPS and fix mixed content, broken cert chains, and HTTP-only endpoints. HSTS turns every HTTP URL into HTTPS, so anything that does not work over HTTPS breaks.
Expected: clean HTTPS loads across the site and subdomains, verified in a fresh browser profile. Pay special attention to staging, API, and static-asset subdomains people forget.
2. Add the header with a short max-age
Start with a five-minute max-age and no subdomain or preload directives.
curl -sI YOUR_HOST | grep -i strict-transportExpected: a header like Strict-Transport-Security: max-age=300. A short max-age means any mistake washes out of browsers in minutes, not months.
3. Watch, then raise max-age in stages
Run at 300 for a few days, then step up through a day, a week, and a month, checking for reports of breakage at each stage. Monitor your error rates and support tickets.
Expected: no new breakage at each stage before moving up. Rushing from 300 seconds to a year in one deploy skips the safety the staging provides.
4. Add subdomains only when they are all clean
Once the main domain is stable at a long max-age, add the subdomain directive, but only if every subdomain is HTTPS-clean, including the ones nobody maintains anymore.
Expected: a header value of max-age=31536000 with the subdomain directive included, serving without incident. One forgotten HTTP-only internal subdomain becomes unreachable to every browser that saw the header; audit first.
5. Consider preload last, and deliberately
Preload bakes your domain into browsers themselves and is very hard to undo. Submit only when the long max-age plus subdomain coverage has run clean for months.
Expected: a deliberate decision with sign-off, not a checkbox ticked during initial setup. Removal from the preload list takes months and requires every browser to ship an update.
Variant: rolling back an HSTS mistake
Drop the max-age to a tiny value and serve it from HTTPS; browsers will pick up the shorter expiry on their next visit. You cannot reach browsers that never come back, which is why the short stages in step 2 matter.
Variant: HSTS on APIs and single-page apps
Same header, same staging, but verify every client: mobile apps with pinned HTTP calls and old integrations break silently where browsers show errors.
Why this happens
HSTS is a cached instruction to the browser, not a server setting you can flip back instantly. The browser remembers it for the full max-age, so a premature long max-age plus one HTTP-only subdomain equals a self-inflicted outage that outlasts your deploy rollback.
Edge cases and pitfalls
- The subdomain directive covering a subdomain you do not control or forgot about.
- Preload submission before the setup is proven; treat it as nearly irreversible.
- Certificate renewals: HSTS plus an expired cert means no click-through bypass for users.
- Local development hostnames affected by subdomain directives in shared browsers.
- CDN or proxy stripping the header; verify with curl from outside, not just in your config.
Provenance
Resolved from the public thread: https://vectle.com/posts/pst_4Ofu2vAU0ibBi9JjT5GS9Q