HTTP Strict Transport Security, or HSTS, is one of those headers that looks trivial until you ship the wrong value and lock yourself into a bad decision for months.
If you deploy on Netlify, adding HSTS is easy. Adding it safely takes a bit more thought.
This guide covers the practical side: what HSTS actually does, how to configure it on Netlify, how to avoid common mistakes, and how I’d roll it out on a real production site.
What HSTS does
HSTS tells browsers:
- only use HTTPS for this site
- automatically rewrite future HTTP requests to HTTPS
- optionally apply the rule to all subdomains
- optionally qualify the domain for browser preload lists
A typical header looks like this:
Strict-Transport-Security: max-age=31536000; includeSubDomains
That means:
max-age=31536000: remember this rule for 1 yearincludeSubDomains: apply it to all subdomains too
Once a browser sees that header over a valid HTTPS connection, it will stop trying plain HTTP for that host during the policy lifetime.
That matters because a plain HTTP first request is where downgrade attacks and SSL stripping happen. HSTS closes that gap after the first secure visit.
What HSTS does not do
HSTS does not replace:
- valid TLS certificates
- redirecting HTTP to HTTPS
- secure cookie settings
- CSP, X-Frame-Options, or other security headers
It also does not help if a user has never visited your site before, unless your domain is on the browser preload list.
Why HSTS on Netlify is straightforward
Netlify already gives you HTTPS and can force redirects to HTTPS. HSTS is just another response header, so you can configure it through:
- a
_headersfile netlify.toml
I prefer netlify.toml for maintainability if the project already uses it. If the site is static and header rules are simple, _headers is perfectly fine.
Before you enable HSTS
Do these checks first.
1. Make sure HTTPS works everywhere
Every hostname you care about must serve valid HTTPS:
- apex domain, like
example.com wwwsubdomain, if used- any other subdomains if you plan to use
includeSubDomains
If you set includeSubDomains and one of your subdomains still serves over HTTP only, you just broke that subdomain for HSTS-capable browsers.
2. Redirect HTTP to HTTPS
Netlify can enforce HTTPS, and you should absolutely do that before HSTS. HSTS tells browsers what to do on future requests. Redirects still matter for first-time visitors and non-HSTS clients.
3. Know your subdomain inventory
This is the part teams skip.
If your company has old subdomains like:
status.example.comdocs.example.comold-api.example.comdev.example.com
and they aren’t all HTTPS-ready, don’t use includeSubDomains yet.
4. Start with a short max-age
I never start at one year. I start small, verify behavior, then increase it.
A sane rollout looks like:
- 5 minutes
- 1 day
- 1 week
- 1 year
That gives you room to back out.
Option 1: Configure HSTS with netlify.toml
Here’s a practical Netlify config:
[[headers]]
for = "/*"
[headers.values]
Strict-Transport-Security = "max-age=300"
That sends HSTS for all routes with a 5-minute policy.
After you verify everything works, increase it:
[[headers]]
for = "/*"
[headers.values]
Strict-Transport-Security = "max-age=86400"
Then later:
[[headers]]
for = "/*"
[headers.values]
Strict-Transport-Security = "max-age=31536000"
If you’re ready to include subdomains:
[[headers]]
for = "/*"
[headers.values]
Strict-Transport-Security = "max-age=31536000; includeSubDomains"
If you’re preparing for preload:
[[headers]]
for = "/*"
[headers.values]
Strict-Transport-Security = "max-age=31536000; includeSubDomains; preload"
Be careful with that last one. preload is not just a suggestion. It means you intend to meet preload requirements consistently.
Option 2: Configure HSTS with a _headers file
For static sites, put a _headers file in your publish directory or source directory depending on your build setup.
Example:
/*
Strict-Transport-Security: max-age=300
Later, increase it:
/*
Strict-Transport-Security: max-age=31536000; includeSubDomains
Same rules, same risks.
A realistic Netlify setup
Here’s a netlify.toml example I’d actually ship for a production site, including redirects and a few core security headers:
[[redirects]]
from = "http://example.com/*"
to = "https://example.com/:splat"
status = 301
force = true
[[redirects]]
from = "http://www.example.com/*"
to = "https://www.example.com/:splat"
status = 301
force = true
[[headers]]
for = "/*"
[headers.values]
Strict-Transport-Security = "max-age=86400"
X-Frame-Options = "DENY"
X-Content-Type-Options = "nosniff"
Referrer-Policy = "strict-origin-when-cross-origin"
After a few days without issues, I’d bump HSTS to a longer lifetime.
How to verify HSTS is being sent
You don’t want to guess here. Check the actual response headers.
Using curl
curl -I https://example.com
You want to see something like:
HTTP/2 200
strict-transport-security: max-age=86400
x-frame-options: DENY
x-content-type-options: nosniff
referrer-policy: strict-origin-when-cross-origin
Also test redirects from HTTP:
curl -I http://example.com
You should see a redirect to HTTPS.
Using a browser
Open dev tools, load the page over HTTPS, and inspect the response headers in the network tab.
Using a scanner
If you want a quick external check, run your domain through Headertest. It’s useful for spotting missing or malformed security headers fast.
Common HSTS mistakes on Netlify
These are the ones I see most often.
Setting a huge max-age too early
If you start with:
Strict-Transport-Security: max-age=31536000; includeSubDomains
on day one, you’ve committed browsers for a year. If you missed a subdomain or certificate issue, users can get stuck.
Start small.
Using includeSubDomains without auditing subdomains
This one breaks staging, old apps, forgotten services, and weird internal tools exposed on public DNS.
If you don’t fully control every subdomain, don’t set it yet.
Adding preload because it sounds better
A lot of people treat preload like a security badge. It’s really a long-term operational commitment.
Preloading means browsers hardcode your domain as HTTPS-only before the first visit. That’s great when you’re ready. It’s painful if you aren’t.
I’d only add:
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
when all of these are true:
- your whole domain is HTTPS-only
- every subdomain is HTTPS-ready
- HTTP redirects cleanly to HTTPS
- you’re comfortable keeping that policy long-term
Sending HSTS on HTTP instead of HTTPS
Browsers only honor HSTS when received over a valid HTTPS connection. If you’re testing over HTTP and don’t see it applied, that’s expected.
Forgetting custom domains
Netlify preview URLs and your production custom domain are not the same thing operationally.
What matters most is the domain users actually visit. Verify HSTS there, not just on a generated Netlify subdomain.
Safe rollout plan
If I were enabling HSTS on a Netlify production site today, I’d do this:
Phase 1: enable HTTPS redirects
Make sure all HTTP traffic gets redirected to HTTPS.
Phase 2: set a short HSTS lifetime
[[headers]]
for = "/*"
[headers.values]
Strict-Transport-Security = "max-age=300"
Watch for certificate issues, mixed environment weirdness, and forgotten hostnames.
Phase 3: increase gradually
Move to:
Strict-Transport-Security = "max-age=86400"
Then:
Strict-Transport-Security = "max-age=604800"
Then:
Strict-Transport-Security = "max-age=31536000"
Phase 4: decide on includeSubDomains
Only after you’ve audited subdomains.
Phase 5: decide on preload
Only if you want the operational commitment and meet the requirements.
How to disable HSTS if you need to back out
If you catch a problem early, send:
Strict-Transport-Security: max-age=0
Netlify example:
[[headers]]
for = "/*"
[headers.values]
Strict-Transport-Security = "max-age=0"
That tells browsers to forget the policy.
But there’s a catch: browsers have to successfully reach your site over HTTPS and receive that new header first. If users are already stuck and your HTTPS setup is broken, you can’t rely on this as an emergency escape hatch.
That’s why short initial max-age values are the smart move.
Recommended production value
For a stable production site on Netlify that fully supports HTTPS, this is the standard target:
[[headers]]
for = "/*"
[headers.values]
Strict-Transport-Security = "max-age=31536000; includeSubDomains"
If you haven’t audited subdomains, use this instead:
[[headers]]
for = "/*"
[headers.values]
Strict-Transport-Security = "max-age=31536000"
That still gives strong protection for the main host without risking subdomain breakage.
Netlify-specific docs
For the platform details on custom headers and configuration formats, check the official Netlify documentation:
HSTS is easy to add on Netlify. The hard part is resisting the urge to copy a one-year preload policy without thinking through your domain setup first.
My advice: ship redirects, start with a tiny max-age, verify with curl, scan with Headertest, then ramp up deliberately. That’s boring, and boring is exactly what you want from transport security.