Response header
< Strict-Transport-Security: max-age=63072000; includeSubDomains; preloadStrict-Transport-Security (HSTS) Header: Setup and Preload
Strict-Transport-Security (HSTS) forces HTTPS for a set time. max-age, includeSubDomains, preload rules, nginx and Apache config, and how to roll it back.
- Direction
- Response
- Category
- Security
- Spec
- RFC 6797 §6.1
TL;DR:
Strict-Transport-Security: max-age=31536000; includeSubDomainsmakes browsers use HTTPS only for your site for a year. Roll it out with a shortmax-agefirst, because a mistake locks out anyone whose HTTPS breaks, and preloading is very hard to reverse.
What it does
The first visit to http://example.com is vulnerable: an attacker on the network can intercept it and keep the victim on HTTP (SSL stripping). A redirect to HTTPS does not help if the redirect itself is stripped. HSTS closes that gap for later visits:
HTTP/2 200
Strict-Transport-Security: max-age=31536000; includeSubDomains
For the next year the browser converts http://example.com/... into https://example.com/... locally, with no network request, and turns certificate warnings into hard errors with no “proceed” button. In Chrome that is the “Your connection is not private” page with NET::ERR_CERT_AUTHORITY_INVALID and no Advanced link; Chrome DevTools shows the local rewrite as 307 Internal Redirect with Non-Authoritative-Reason: HSTS.
Defined in RFC 6797. The header is ignored over plain HTTP, over HTTPS with an invalid certificate, and on IP-address hosts.
Directives
| Directive | Meaning |
|---|---|
max-age=<seconds> | How long the policy lasts, refreshed on every response with the header. 0 clears it |
includeSubDomains | Applies to every subdomain, including internal ones such as intranet.example.com |
preload | Signals consent to be on the browser-shipped preload list. The header does not add you; submission does |
Safe rollout
- Make sure every URL you serve works on HTTPS, including subdomains if you use
includeSubDomains, and that HTTP redirects with 301 or 308 to HTTPS. - Send
max-age=300and watch for problems. - Step up:
86400,604800, then31536000. - Add
includeSubDomainsonly after every subdomain, including old staging and mail hosts, is on valid HTTPS. - Consider preload last.
Preload requirements
From hstspreload.org:
- A valid certificate and redirect from HTTP to HTTPS on the same host.
- All subdomains served over HTTPS.
- The header on the apex domain’s HTTPS response with
max-ageof at least31536000(one year),includeSubDomains, andpreload.
Strict-Transport-Security: max-age=63072000; includeSubDomains; preload
Submit at hstspreload.org. Removal is possible but it ships with browser releases, so users may be locked to HTTPS for months. Do not preload if any subdomain cannot be HTTPS.
Configuration
nginx. Use always so error responses carry it too. add_header in a location discards inherited add_header lines, so put it at server level or repeat it.
server {
listen 443 ssl;
http2 on;
server_name example.com;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
}
server {
listen 80;
server_name example.com;
return 301 https://$host$request_uri;
}
Apache:
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"
Express (helmet). Helmet’s default is max-age=31536000; includeSubDomains:
const helmet = require('helmet')
app.use(helmet.hsts({ maxAge: 31536000, includeSubDomains: true, preload: false }))
Django:
SECURE_HSTS_SECONDS = 31536000
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
SECURE_HSTS_PRELOAD = False
SECURE_SSL_REDIRECT = True
Behind a TLS-terminating proxy or Cloudflare. Set it on the layer that speaks HTTPS to clients. Cloudflare has an HSTS toggle under SSL/TLS, Edge Certificates. Do not also set it at the origin with different values.
Verify
curl -sI https://example.com | grep -i strict-transport-security
strict-transport-security: max-age=31536000; includeSubDomains
In Chrome, chrome://net-internals/#hsts lets you query a domain and delete its policy, which is the fast way to recover your own browser after a bad rollout. Firefox users clear it with “Forget about this site”.
Gotchas
- The first visit is still unprotected unless the domain is preloaded; HSTS is “trust on first use”.
localhostand raw IPs are not affected, but a custom dev domain with HSTS andincludeSubDomainscan break localhttp://dev.example.comsetups.- A max-age of 0 over HTTPS is the only way to cancel; letting the cert expire locks users out instead.
- Cookies are not made secure by HSTS; still set the Secure flag.
Related
- HTTPS and TLS guide, Set-Cookie, Content-Security-Policy (
upgrade-insecure-requests) - Site verifier tool
See also the comparison HTTP vs HTTPS: what TLS adds, what it still leaves visible, and HSTS.
Frequently asked questions
What does Strict-Transport-Security do?
After a browser receives it over HTTPS, the browser rewrites every http:// request for that host to https:// internally for max-age seconds, and refuses to let the user click through certificate errors. It protects against SSL stripping and downgrade attacks on later visits.
What max-age should I use for HSTS?
Start at 300 to 86400 seconds while you confirm everything works over HTTPS, then move to 31536000 (one year). Preload requires at least 31536000. The clock is reset on every response that carries the header.
What are the HSTS preload requirements?
Serve a valid certificate, redirect HTTP to HTTPS on the same host, serve all subdomains over HTTPS, and send the header on the HTTPS response for the apex domain with max-age of at least 31536000, includeSubDomains and preload. Then submit at hstspreload.org. Removal takes months to reach all users.
Why is HSTS ignored on my HTTP site?
Browsers ignore the header when received over plain HTTP, over HTTPS with an invalid certificate, or from an IP address. It only takes effect from a valid HTTPS response on a domain name.
How do I undo HSTS?
Serve Strict-Transport-Security: max-age=0 over HTTPS; browsers drop the policy on next contact. Visitors who do not return keep it until the old max-age lapses. If the domain is preloaded, you must also request removal at hstspreload.org and wait for browser releases.
Why does nginx not send my HSTS header on error pages?
add_header only applies to 2xx and 3xx responses unless you append the always parameter. Use add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; and note that add_header lines in a location block replace those inherited from the server block.
Sources
Related
HTTP Security Headers Checklist: Values and Configs
Which HTTP security headers to send and which to drop: starting values for CSP, HSTS and more, with nginx, Apache, Express, Next.js and Cloudflare config.
Access-Control-Allow-Credentials Header
Learn how Access-Control-Allow-Credentials controls whether browsers expose responses when credentials (cookies, auth headers) are included in CORS requests.
Access-Control-Allow-Headers Header
Learn how Access-Control-Allow-Headers specifies which custom HTTP headers can be used during cross-origin requests in CORS preflight responses.
Access-Control-Allow-Methods Header
Learn how Access-Control-Allow-Methods specifies which HTTP methods are permitted for cross-origin requests in CORS preflight responses.