Security Headers
HSTS explained — force HTTPS and stop downgrade attacks
What the Strict-Transport-Security (HSTS) header does, which max-age to use, what includeSubDomains and preload mean, and copy-paste examples for nginx, Apache and Netlify.
By SurfaceWatch··5 min read
Having an HTTPS certificate isn't enough on its own. If someone types yourdomain.com without https://, the browser's first request goes out over plain HTTP, and that is exactly the moment an attacker on the same network (say, café Wi-Fi) can intercept it or redirect it somewhere else. This is called a downgrade or SSL-stripping attack.
HTTP Strict Transport Security (HSTS) is a response header that closes that gap. After the browser sees it once, it refuses to talk to your site over HTTP for a set time and upgrades every request to HTTPS by itself.
What the header looks like
Strict-Transport-Security: max-age=31536000; includeSubDomains
max-age: how many seconds the browser should remember the rule.31536000is one year.includeSubDomains: apply the rule to every subdomain too.preload: opt in to the browser preload list (see below).
Roll it out safely
HSTS is sticky: once a browser has the rule, you cannot easily switch back to HTTP until it expires. So go step by step:
- Make sure your whole site, and every asset on it, works over HTTPS.
- Start with a short
max-age, such as300(five minutes), and check that nothing breaks. - Raise it to a week, then a month, then a year (
31536000). - Add
includeSubDomainsonly when every subdomain, including old or internal ones, serves HTTPS correctly.
What about preload?
Even with HSTS, the very first visit from a new browser is still unprotected, because it hasn't seen the header yet. The HSTS preload list is a list built into browsers that fixes this. To be listed you need a max-age of at least a year, includeSubDomains and the preload token. Be careful: removal from the list is slow, so only preload when you are sure all subdomains will stay on HTTPS.
Copy-paste examples
nginx
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
Apache
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"
Netlify (_headers file)
/*
Strict-Transport-Security: max-age=31536000; includeSubDomains
The header is only honoured when it is sent over HTTPS, so serve it from your HTTPS responses.
How to check your site
Run curl -sI https://yourdomain.com | grep -i strict-transport. If nothing comes back, you have no HSTS. SurfaceWatch checks it along with your other security headers, flags it when it is missing or too short, and gives you the exact line to paste.
HSTS costs one line of config and removes a whole class of attack. Start short, increase slowly, and then leave it on.
Keep reading
Check your site's HSTS — add SurfaceWatch free
One click, an honest grade, and the exact fix. Free to install. No account needed.