Add HTTP Strict Transport Security
Send a Strict-Transport-Security header only over HTTPS, start with a short max-age, verify it at the edge, and understand the commitment of preload.
Task
Enforce HTTPS for future requests with HSTS without locking users out of a domain that is not fully HTTPS-ready.
When to use this
A site redirects HTTP to HTTPS, but the first request still travels over plaintext and can be intercepted. HSTS tells the browser to use HTTPS before making the request.
Prerequisites
- A site served over HTTPS with a valid certificate
- Control of the edge, reverse proxy, or application response headers
- Every subdomain that will be covered by includeSubDomains served over HTTPS
Steps
1. Add the header only on HTTPS responses, starting with a short max-age for testing.
app.use((req, res, next) => {
if (req.secure) {
res.setHeader('Strict-Transport-Security', 'max-age=300');
}
next();
});
Expected output: HTTPS responses include Strict-Transport-Security: max-age=300; HTTP responses do not.
2. Verify the header through the public edge, including redirects, and confirm there is no duplicate or conflicting value.
curl -sI https://example.com | grep -i strict-transport-securityExpected output: One header line with the expected max-age.
3. Increase max-age to one year and add includeSubDomains only after every covered subdomain is HTTPS-only.
Strict-Transport-Security: max-age=31536000; includeSubDomains
Expected output: Browsers remember HTTPS for one year and refuse HTTP for covered hosts.
4. Consider preload only after the domain has served the full policy for a sustained period and the preload submission requirements are met.
curl -sI https://hstspreload.org | head -n 1Expected output: A deliberate decision recorded in the runbook, not an accidental preload submission.
Troubleshooting
Problem
The header is missing at the public edge
Solution
The TLS terminator may strip or overwrite headers. Set HSTS at the edge or ensure the proxy forwards the application header.
Problem
A subdomain breaks after includeSubDomains
Solution
Remove includeSubDomains until every subdomain has a valid certificate and HTTPS listener; browsers that cached the policy will still enforce it.
Limitations
- HSTS is ignored on the first HTTP request, so it does not replace an HTTP-to-HTTPS redirect.
- Preload removal is slow and depends on browser updates; do not preload a domain you cannot keep HTTPS-only.
Start short, then commit
HSTS is a promise that the browser will not use HTTP for the domain until the max-age expires. A typo in the policy can make a site unreachable for months. Start with a short max-age on HTTPS responses, verify it through the public edge, then increase it. Do not send the header on HTTP responses; browsers ignore it there, and a proxy that adds it indiscriminately can confuse debugging.
Strict-Transport-Security: max-age=300
After testing, move to max-age=31536000. Add includeSubDomains only when every subdomain, including forgotten internal or marketing hostnames, serves valid HTTPS. The browser applies the policy to the whole registrable domain, not just the host that sent it.
Verify at the edge
Application code is not the only place headers are set. A CDN, load balancer, or framework may add a second value. Check with curl -sI from outside the network and inspect redirects. A response with two conflicting HSTS headers is invalid and may be ignored.
Preload is a one-way door
The HSTS preload list ships in browsers. Removal can take months and requires a submission update plus browser releases. Preload only a domain that is HTTPS-only for all subdomains for the foreseeable future. Record the decision, the date, and the owner. HSTS does not replace the HTTP-to-HTTPS redirect for first-time visitors, and it does not protect the very first request from a network attacker.