Skip to content

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. 31536000 is 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:

  1. Make sure your whole site, and every asset on it, works over HTTPS.
  2. Start with a short max-age, such as 300 (five minutes), and check that nothing breaks.
  3. Raise it to a week, then a month, then a year (31536000).
  4. Add includeSubDomains only 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.