HSTS and HSTS Preload: Complete Guide to Forced HTTPS
HSTS and HSTS Preload: Complete Guide to Forced HTTPS
HTTP Strict Transport Security (HSTS) is a security mechanism that tells the browser: “for this domain, always use SSL/TLS проверку, even if the user typed http://”. Preload goes further: the domain ships inside browsers before the first visit. Correctly configured HSTS protects against MITM on the first connection; wrong config can lock your site offline for months. Here's how to do it safely.
How HSTS works
Server returns the Strict-Transport-Security header on HTTPS responses:
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
Directives:
max-age— seconds the browser remembers the rule (usually 1 year = 31536000).includeSubDomains— applies to all subdomains.preload— consent to be added to the preload list (doesn't automatically add you).
After receiving the header, the browser caches the rule. The next request to http://example.com is rewritten internally to https:// without hitting the network. This prevents SSL-stripping: a Wi-Fi attacker can't downgrade HTTPS to HTTP.
First-visit limitation (TOFU)
HSTS is Trust-On-First-Use — the rule enters the browser only after a first HTTPS visit. If the user's first request is plain HTTP, the attacker can proxy it and stay on HTTP. Fix — preload list: your domain is baked into Chrome/Firefox/Safari/Edge source code, and HSTS applies before any visit.
Step-by-step HSTS rollout
Step 1: make sure everything is HTTPS
All subdomains, CDNs, API документацию, and images must work on HTTPS. Eliminate mixed content — see mixed content fix.
Step 2: short max-age for testing
# nginx
add_header Strict-Transport-Security "max-age=300" always;
300 seconds (5 min) is long enough to check the site works. If something breaks, the rule expires in 5 minutes. Test with DevTools and enterno.io Security Scanner.
Step 3: raise max-age gradually
add_header Strict-Transport-Security "max-age=86400" always; # 1 day
# one week later
add_header Strict-Transport-Security "max-age=2592000" always; # 1 month
# one month later
add_header Strict-Transport-Security "max-age=31536000" always; # 1 year
Step 4: add includeSubDomains
Only when you're sure every subdomain serves HTTPS. Otherwise users lose access to blog.example.com if it's still HTTP.
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
Step 5: submit to preload list
After a month with long max-age and includeSubDomains — submit to hstspreload.org.
Preload list requirements
- Valid cert on apex and all subdomains.
- HTTP redirects to HTTPS with 301.
- HTTPS on apex serves the HSTS header.
- Header includes all three directives:
max-age=31536000; includeSubDomains; preload. - max-age of at least 1 year.
Pre-check: curl -sI https://example.com | grep -i strict-transport. After approval you'll appear in Chrome in 6-12 weeks, other browsers later.
HTTP → HTTPS redirect in nginx
server {
listen 80;
server_name example.com www.example.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
server_name example.com www.example.com;
# ... SSL config ...
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
}
Important: the header must have the always flag or nginx won't attach it to 4xx/5xx responses (and the preload validator will reject it).
Pitfalls
Cannot roll back quickly
Once HSTS is on with a long max-age, you can't turn off HTTPS — every user who cached the rule will fail if HTTPS breaks. Fix: set max-age=0 and wait for all caches to expire (up to a year). Preload list removal takes 6+ months; freshly added domains are removed faster.
Every subdomain must work
includeSubDomains applies to EVERY subdomain, including forgotten ones. Check via Certificate Transparency:
curl "https://crt.sh/?q=%25.example.com&output=json" | jq '.[].name_value' | sort -u
All issued certs — gives you the subdomain list. Each must serve HTTPS or it breaks for users.
Dev/staging environments
If you have dev.example.com on HTTP — includeSubDomains kills it. Fix: move dev to a separate domain (dev.example-dev.com) or enable HTTPS everywhere.
HSTS + Let's Encrypt
Problem: if Let's Encrypt renewal breaks and the cert expires, HSTS prevents users from bypassing the warning. This stops MITM but amplifies the damage of a real error (see expired cert). Fix — robust monitoring: Enterno.io Monitors with alerts at 14/7/3 days before expiry, plus SMS escalation for critical services.
Config verification
- enterno.io Security Scanner — shows HSTS presence and correctness.
- hstspreload.org — preload readiness validator.
- SSL Labs — shows HSTS and overall TLS grade.
- DevTools → Application, or
chrome://net-internals/#hsts— inspect what your browser cached.
Frequently asked questions
Do I need HSTS if everything is already HTTPS?
Yes. HSTS protects against the first HTTP request. Without preload, the first visit is still vulnerable to SSL-stripping.
How do I remove a domain from the preload list?
File a removal request at hstspreload.org. Processing takes 3-12 months. Recently added domains are faster; long-lived preload entries take longer.
Does HSTS cover subdomains without their own header?
With includeSubDomains — yes, the rule is inherited. But each subdomain must still serve a valid SSL cert.
Can I have HSTS without HTTP→HTTPS redirect?
Technically yes, but preload requires a redirect. Without it, a user typing example.com gets an HTTP failure (if no HTTP server) or an HTTP page (if there is), defeating the purpose.
Conclusion
HSTS with preload is table stakes for a modern HTTPS site. Setup is simple but requires discipline: start with a short max-age, test, add includeSubDomains, then submit for preload. Check readiness via Security Scanner, guard against regressions via Monitors. Related: mixed content and weak cipher suites.
HSTS — RFC 6797. Preload — hstspreload.org. OWASP HSTS Cheat Sheet — cheatsheetseries.owasp.org.
Check your website right now
Check now →