# 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.

```text
how to set HSTS without locking yourself out
```

## Use 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.

```bash
curl -sI YOUR_HOST | grep -i strict-transport
```

Expected: 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
