Antoine Duno
Founder of ZeriFlow · 10 years fullstack engineering · About the author
Key Takeaways
- HSTS (HTTP Strict Transport Security) is a single header that eliminates an entire class of SSL stripping attacks. This guide explains how it works, how to configure it safely, and what you must check before enabling the preload flag.
- Includes copy-paste code examples and step-by-step instructions.
- Free automated scan available to verify your implementation.
HSTS Explained: How to Enable HTTP Strict Transport Security
HSTS — HTTP Strict Transport Security — is one of the most impactful security headers you can add to your site, and one of the simplest. A single line in your server configuration tells every browser that has visited your site to reject plain HTTP connections entirely, replacing them with HTTPS automatically.
This guide explains what HSTS does at the protocol level, how to configure it correctly, what the preload directive actually means, and the pitfalls that can lock your users out of your site if you rush the deployment.
The Problem HSTS Solves
HTTPS protects data in transit by encrypting the connection between the browser and your server. But there is a window of vulnerability in the very first request.
When a user types yourdomain.com in their browser — without specifying https:// — the browser sends an initial HTTP request. Even if your server immediately redirects to HTTPS with a 301, that first HTTP request has already gone out unencrypted. An attacker on the same network (a coffee shop, a hotel Wi-Fi, a corporate proxy) can intercept that first request before the redirect happens. They can then serve the user a fake HTTP version of your site, strip all HTTPS links, and sit in the middle of the connection indefinitely. This is called an SSL stripping attack.
HSTS solves this by storing a rule in the browser itself: "for this domain, always use HTTPS, regardless of what the URL says." After a browser has seen your HSTS header once, it will never make a plain HTTP request to your domain again during the max-age period — even if the user types http:// explicitly.
The HSTS Header
Strict-Transport-Security: max-age=31536000; includeSubDomains; preloadThis is a single header with up to three directives. Let''s break each one down.
max-age: How Long the Rule Lasts
Strict-Transport-Security: max-age=31536000max-age is specified in seconds. When a browser receives this header, it stores the HSTS policy for that origin and the rule''s expiry time. For the next max-age seconds, every request to that domain is automatically upgraded to HTTPS — without any round-trip to your server.
The max-age value resets to its maximum every time the browser receives the HSTS header on a valid HTTPS response. So as long as a user visits your site at least once during the max-age window, the policy stays fresh.
Recommended values:
| Use Case | max-age |
|---|---|
| Testing / initial deployment | max-age=300 (5 minutes) |
| Early production | max-age=86400 (1 day) |
| Stable production | max-age=2592000 (30 days) |
| Hardened production | max-age=31536000 (1 year) |
| HSTS preload requirement | max-age=31536000 (minimum) |
Start low during testing and ramp up as confidence grows. The risk of starting high is being locked out of HTTP fallback if you discover a problem.
includeSubDomains: Extending Protection
Strict-Transport-Security: max-age=31536000; includeSubDomainsWithout includeSubDomains, HSTS only applies to the exact origin (e.g. www.yourdomain.com) but not to any subdomain (e.g. api.yourdomain.com, blog.yourdomain.com, admin.yourdomain.com).
An attacker who cannot MITM your main domain might still be able to MITM a subdomain, set a cookie that gets sent to the main domain, and use that for an attack. HSTS with includeSubDomains closes this gap.
Before enabling `includeSubDomains`: - Ensure every subdomain is accessible over HTTPS. Any subdomain that is HTTP-only will become unreachable for browsers that have seen the HSTS header. - This includes subdomains you might have forgotten about: old staging environments, internal tools exposed on subdomains, third-party services running on your subdomains.
Run a DNS audit of your domain before enabling this.
preload: The Browser Preload List
Strict-Transport-Security: max-age=31536000; includeSubDomains; preloadThe preload directive is different from the other two — it is not a browser instruction, it is a signal. It tells the browser (and preload list maintainers) that you want your domain included in a hardcoded list of HSTS-forced domains that is shipped with every major browser.
The HSTS preload list is maintained at hstspreload.org and is bundled into Chrome, Firefox, Safari, and Edge. Domains on this list get HSTS enforcement from the very first visit — even before any HTTPS response has been received — completely eliminating the first-visit vulnerability.
How to Get on the Preload List
1. Your site must serve a valid HSTS header with:
- max-age of at least 31536000 (1 year)
- includeSubDomains present
- preload present
2. All subdomains must be accessible over HTTPS
3. Your site must redirect HTTP to HTTPS
4. Submit at hstspreload.org
Inclusion takes weeks to months. Removal takes even longer — typically 3–6 months before browsers ship an update that removes your domain.
The Removal Problem
This is the most important point about preload: once you are on the list, removing yourself is a slow process. If you ever need to drop HTTPS for a subdomain, serve HTTP on any subdomain, or change your TLS configuration in a way that breaks HTTPS, you cannot simply remove the header and expect users to be able to access the site the next day. Browsers that already have the preloaded list will continue to enforce HTTPS.
Do not submit to the preload list unless: - You are confident your entire domain tree (including all subdomains) will remain on HTTPS permanently - You have tested thoroughly on a staging domain first - You understand you cannot easily undo this
How to Enable HSTS
Nginx
server {
listen 443 ssl;
server_name yourdomain.com;
# ... your SSL config ...
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
}
# Redirect HTTP to HTTPS
server {
listen 80;
server_name yourdomain.com www.yourdomain.com;
return 301 https://$host$request_uri;
}Critical: Only add the HSTS header to your HTTPS server block, not the HTTP one. The header is meaningless on HTTP (and browsers ignore it) — but more importantly, setting it on the HTTP redirect means a user who lands on HTTP and is redirected never gets to see the HSTS header on a trusted HTTPS response.
Apache
<VirtualHost *:443>
ServerName yourdomain.com
# ... your SSL config ...
<IfModule mod_headers.c>
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
</IfModule>
</VirtualHost>
<VirtualHost *:80>
ServerName yourdomain.com
Redirect permanent / https://yourdomain.com/
</VirtualHost>Caddy
Caddy enables HSTS automatically when you configure a domain with HTTPS. To set a custom max-age:
yourdomain.com {
header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
}Node.js / Express with Helmet
import helmet from ''helmet'';
app.use(helmet.hsts({
maxAge: 31536000, // 1 year in seconds
includeSubDomains: true,
preload: true,
}));Cloudflare
In the Cloudflare dashboard: SSL/TLS > Edge Certificates > HTTP Strict Transport Security (HSTS). Toggle it on and configure the settings. Note that Cloudflare applies the header at the edge, not at your origin server.
Verifying HSTS Is Working
Check the response header:
curl -s -I https://yourdomain.com | grep -i strict
# Expected output:
# strict-transport-security: max-age=31536000; includeSubDomains; preloadCheck browser storage: In Chrome, navigate to chrome://net-internals/#hsts. Enter your domain name and click "Query". You should see your domain''s HSTS policy.
Check preload status: Visit hstspreload.org and enter your domain to see its current preload status.
Automated check: Run a scan at ZeriFlow — it verifies HSTS is present, checks the max-age value against recommended thresholds, and flags missing includeSubDomains or preload directives.
Common Mistakes
Setting HSTS on HTTP responses: The browser ignores HSTS headers delivered over HTTP (they could be injected by a MITM). The header is only respected when delivered over a valid HTTPS response.
Short max-age in production:
A max-age of 300 or 3600 is fine for testing, but means the protection only lasts minutes to hours after each visit. Real-world security requires at least 30 days (2592000).
Enabling `includeSubDomains` before auditing subdomains: The most common HSTS mistake. One forgotten HTTP-only subdomain becomes permanently inaccessible to users who have seen the header.
Conflating HSTS and HTTPS: HSTS does not encrypt your traffic — your TLS certificate does that. HSTS just ensures the browser never sends unencrypted requests. You need both.
Adding `preload` without submitting:
The preload directive in the header is just a declaration of intent. Being in browsers'' preload lists requires submitting to hstspreload.org. The header alone does nothing extra.
HSTS and Mixed Content
If your pages load any resources over HTTP (images, scripts, fonts), the browser will either block those resources (if CSP''s upgrade-insecure-requests is not set) or silently show them with a broken padlock. HSTS does not fix mixed content — it only affects navigation. Fix mixed content separately by:
- 1Auditing your HTML and CSS for
http://resource URLs - 2Setting
Content-Security-Policy: upgrade-insecure-requeststo auto-upgrade HTTP resources to HTTPS - 3Checking server-rendered database content that might contain legacy HTTP URLs
Deployment Checklist
Before enabling HSTS in production:
- [ ] Your site serves all pages over HTTPS, including all subdomains
- [ ] Your TLS certificate is valid and will not expire soon
- [ ] HTTP traffic redirects to HTTPS with a permanent (301) redirect
- [ ] You have audited all subdomains — none are HTTP-only
- [ ] You have tested with a short
max-agefirst (300–3600 seconds) - [ ] You have verified the header appears on live HTTPS responses
- [ ] (If preload) You understand removal takes months and have submitted at hstspreload.org
HSTS is one of the most effective headers available. The vast majority of sites should be running it with at least max-age=31536000; includeSubDomains. If you are not sure whether your site has HSTS configured correctly, run a free scan at ZeriFlow to check.
Check all your security headers instantly — free.
See exactly which headers are missing and how to fix them.