The entire HSTS mechanism lives in one response header — Strict-Transport-Security: max-age=31536000; includeSubDomains; preload — and that brevity is exactly why it gets misconfigured. The header tells a browser to refuse plain HTTP to a domain for a given number of seconds, closing the window an attacker gets on a coffee-shop Wi-Fi network to downgrade a connection before a redirect fires. But per the Mozilla Developer Network's specification, the browser only honors the header when it arrives over an already-secure HTTPS connection; sent over plain HTTP, it's silently ignored, which means the very first request a new visitor makes is never protected by it. The includeSubDomains directive extends the policy to every subdomain of the origin, and the preload flag — itself non-standard and inert without a separate submission step — signals eligibility for the browser-shipped HSTS preload list maintained at hstspreload.org. Helmet, the most widely used Express security-headers middleware, ships an hsts module defaulting to a 365-day max-age with includeSubDomains on and preload off. This piece walks through what each flag actually does, where Node and Express developers get it wrong, and how to roll HSTS out without breaking a subdomain you forgot you own.
What does max-age actually protect against, and what does it miss?
max-age sets, in seconds, how long a browser caches the instruction to force HTTPS for that origin before it will trust plain HTTP again. A value of 31536000 is one year, the minimum MDN and hstspreload.org both treat as a meaningful commitment; shorter values like 86400 (one day) are reasonable for an initial rollout because a misconfiguration self-heals within a day instead of persisting for months. What max-age cannot do is protect the very first connection: a user typing a bare domain into the address bar, or clicking an old http:// link, still makes one plain-HTTP request before any HSTS header can be received and cached. That gap is precisely what the preload list exists to close, since browsers consult it before making any connection at all, independent of whether the site has ever been visited. Teams sometimes assume setting max-age alone delivers preload-level protection — it doesn't; it only protects repeat visits after the header has already been seen once.
Why is includeSubDomains the flag most likely to break something?
includeSubDomains applies the HTTPS-only policy to every subdomain of the origin, not just the one that sent the header, and every one of those subdomains must already serve valid HTTPS before you enable it. This is the flag most Node.js teams get burned by, because a company's DNS zone routinely accumulates subdomains nobody remembers — a staging environment on staging.example.com, a marketing microsite on promo.example.com, an old demo box — and once a browser caches includeSubDomains from example.com, every one of those becomes unreachable over HTTP with no user-facing error beyond a connection failure. Unlike a bad max-age, which self-corrects when the value expires, a cached includeSubDomains policy persists in the browser for the full max-age window regardless of what you change on the server afterward. The safe sequence is to audit your DNS zone for every live subdomain, confirm each terminates TLS correctly, and only then add includeSubDomains to the header — not the other way around.
What are the actual requirements to get on the HSTS preload list?
hstspreload.org, the submission service Chrome, Firefox, and Safari all draw their preload lists from, requires four things before it will accept a domain: a valid TLS certificate, an HTTP-to-HTTPS redirect on the same host, HTTPS served correctly on all subdomains, and an HSTS header on the base domain carrying max-age of at least 31536000, plus both includeSubDomains and preload present simultaneously. Submission itself is a manual form on the site, not an automatic crawl trigger — nothing happens until you submit it. The catch is on the way out: removal from browser-shipped preload lists is not instant, because the list ships baked into browser binary releases, and site operators who submit prematurely have reported the process taking months to fully propagate a removal across all major browsers. Treat preload submission as a one-way door — only submit after includeSubDomains has run cleanly in production for a full max-age cycle with no subdomain failures.
How should HSTS be configured in Express and behind a reverse proxy?
Helmet's hsts middleware is the standard way to set the header from Express code, and its documented defaults are max-age=31536000 with includeSubDomains true and preload false — meaning an unconfigured app.use(helmet()) call already opts into a one-year policy, which is safe as a baseline but shouldn't be treated as the finished configuration for a domain with untested subdomains. The more common architectural mistake is where the header gets set at all: in most production deployments, TLS terminates at a reverse proxy, load balancer, or CDN in front of the Node process, so setting HSTS again inside every backend Express app is redundant and creates drift risk if services disagree on max-age. The header should be set once, at the TLS-terminating hop. A related and frequently missed step is that an Express app enforcing an HTTPS redirect behind a proxy needs app.set('trust proxy', 1) (or the correct hop count) and must check req.get('X-Forwarded-Proto') rather than req.secure, because without it the app sees every request as HTTP from the proxy's local connection and can enter a redirect loop against clients that already arrived over HTTPS.
What does a safe rollout sequence look like in practice?
A safe rollout is staged, not immediate: start with a short max-age such as 86400 and no includeSubDomains, confirm the header is only observed over HTTPS responses, then inventory and verify every subdomain in the zone actually serves valid HTTPS before adding includeSubDomains. Only after that combination has run in production without a subdomain outage should max-age be raised to 31536000 or higher, and only after that should preload be added and a submission made to hstspreload.org. Skipping straight to the preload-ready configuration on a domain you haven't fully audited is the single most common cause of the multi-month recovery scenarios hstspreload.org itself warns about in its submission requirements — the fix is sequencing, not a different header value.