If your site still leaves any room for browsers to touch plain HTTP, you have a weak spot. That’s exactly the problem HSTS solves.
HSTS stands for HTTP Strict Transport Security. It’s a response header that tells browsers: “From now on, only talk to me over HTTPS. Don’t even try HTTP.” Once a browser sees that policy, it stops making insecure requests to your site for a defined period.
That sounds small, but it closes one of the oldest and most annoying gaps in web security: the first insecure request.
The problem HSTS fixes
A lot of sites do this:
- User types
example.com - Browser tries
http://example.com - Server redirects to
https://example.com
That redirect is common, but the first hop is still insecure. On a hostile network, that initial HTTP request can be intercepted or modified before the browser ever reaches HTTPS.
This is where downgrade attacks and SSL stripping come into play. An attacker sitting between the user and your site can interfere with that first request and keep the victim on HTTP, especially if the user never notices the missing lock icon. Users miss this stuff all the time.
Without HSTS, your HTTPS setup is strong only after the browser gets there safely. That “after” is the problem.
With HSTS enabled, a browser that already knows your policy won’t make that insecure first request at all. It upgrades the request to HTTPS locally, before anything goes over the network.
What HSTS actually does
HSTS is delivered through the Strict-Transport-Security response header over an HTTPS connection. A typical header looks like this:
Strict-Transport-Security: max-age=31536000; includeSubDomains
This tells the browser:
- Remember this rule for 31,536,000 seconds, which is one year
- Apply it to this domain
- Also apply it to all subdomains
After receiving that header, the browser stores the policy. If the user later tries to visit http://example.com, the browser silently rewrites it to https://example.com before sending the request.
That’s the core value: the upgrade happens client-side, not via a vulnerable server-side redirect.
Why you need HSTS
If you care about transport security, HSTS is not optional. A valid TLS certificate and a 301 redirect are good baseline hygiene, but they don’t fully solve downgrade risk.
Here’s why I always recommend HSTS on production HTTPS sites.
1. It kills protocol downgrade attacks
An attacker can mess with an HTTP request. They can’t do much if the browser refuses to use HTTP in the first place.
HSTS removes the choice. Once cached, the browser won’t accept insecure transport for that site.
2. It protects users on bad networks
Coffee shop Wi-Fi, hotel portals, random airport networks, corporate proxies with weird behavior — users browse from all kinds of hostile or broken places. HSTS gives your site a stronger default in those environments.
3. It reduces certificate click-through risk
When HSTS is active, browsers become stricter about certificate problems. Users are generally not allowed to bypass major TLS warnings for HSTS-protected sites.
That’s a very good thing. Users should not be clicking through certificate errors on login pages or payment flows.
4. It helps enforce HTTPS everywhere on your domain
A lot of teams think they’ve “moved to HTTPS” when the homepage works over HTTPS. Then six months later, they discover some old subdomain, CDN hostname, or forgotten app still behaves inconsistently. HSTS helps force discipline across the estate, especially with includeSubDomains.
How HSTS works in practice
When your server returns the HSTS header on an HTTPS response, the browser stores the policy. The browser associates that policy with the hostname and expiration time.
A typical flow looks like this:
GET / HTTP/1.1
Host: example.com
HTTP/1.1 200 OK
Strict-Transport-Security: max-age=31536000; includeSubDomains
From that point forward, if the user requests http://example.com, the browser internally upgrades the request to HTTPS.
A couple of constraints matter:
- Browsers only honor HSTS headers received over HTTPS
- If sent over HTTP, the header is ignored
- The policy lasts only as long as
max-age, unless refreshed includeSubDomainsextends the policy to every subdomainpreloadis a separate mechanism, not automatic magic
The anatomy of the HSTS header
The header has a few directives, and each one matters.
max-age
This is required. It sets how long the browser should remember the policy.
Example:
Strict-Transport-Security: max-age=31536000
That means one year.
If you set:
Strict-Transport-Security: max-age=0
you’re telling browsers to delete the cached HSTS policy for that domain. This is how you disable HSTS, though rollback can be messy if you’ve also used preload.
includeSubDomains
This tells the browser to apply HSTS to all subdomains too.
Example:
Strict-Transport-Security: max-age=31536000; includeSubDomains
I like this directive, but only when you actually know your subdomains are HTTPS-ready. If some forgotten internal tool still lives on old-admin.example.com without proper TLS, this setting will break access.
preload
This signals your intent to be included in browser preload lists. Example:
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
Preloading means browsers ship with your domain already hardcoded as HTTPS-only, even on the very first visit. That fixes the “first visit” weakness that normal HSTS alone cannot solve.
But preload is a commitment. Don’t add it casually.
HSTS does not fix everything
This is where people get sloppy.
HSTS is not a replacement for:
- Redirecting HTTP to HTTPS
- Maintaining valid TLS certificates
- Fixing mixed content
- Setting secure cookies
- Using modern TLS settings
- Proper DNS and hosting hygiene
You still need an HTTP-to-HTTPS redirect because not every client supports HSTS, and first-time visitors may not yet have your policy cached unless you’re preloaded.
You still need cookies marked Secure, because HSTS does not rewrite cookie flags for you.
You still need to eliminate mixed content, because loading scripts or assets over HTTP creates a different mess.
A practical HSTS rollout strategy
The biggest mistake I see is teams jumping straight to a one-year policy with includeSubDomains and preload before they’ve audited their environment. That’s how you brick obscure subdomains and annoy everyone.
Do it in stages.
Stage 1: Start small
Use a short max-age first.
Strict-Transport-Security: max-age=300
That’s five minutes. It gives you room to verify behavior safely.
Check:
- Main site works over HTTPS
- HTTP redirects cleanly to HTTPS
- No certificate issues
- No mixed content
- No subdomains depending on HTTP
Stage 2: Increase confidence
Move to something like a week or a month.
Strict-Transport-Security: max-age=2592000
That’s 30 days.
Watch for support tickets, monitoring alerts, and weird legacy services.
Stage 3: Go long-term
Once you’re sure, use a year.
Strict-Transport-Security: max-age=31536000; includeSubDomains
That’s a normal production policy.
Stage 4: Consider preload only if you mean it
Preload is great for mature domains with solid operational control. It is not great for organizations that discover unknown subdomains through panicked Slack messages.
If you can’t say with confidence that every present and future subdomain will support HTTPS properly, hold off.
Server configuration examples
Here are a couple of practical setups.
Nginx
server {
listen 443 ssl http2;
server_name example.com www.example.com;
ssl_certificate /etc/ssl/example/fullchain.pem;
ssl_certificate_key /etc/ssl/example/privkey.pem;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
location / {
proxy_pass http://app_backend;
}
}
server {
listen 80;
server_name example.com www.example.com;
return 301 https://$host$request_uri;
}
The always flag matters in Nginx. Without it, you may miss the header on some non-200 responses, which is not what you want.
Apache
<VirtualHost *:443>
ServerName example.com
ServerAlias www.example.com
SSLEngine on
SSLCertificateFile /path/fullchain.pem
SSLCertificateKeyFile /path/privkey.pem
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"
</VirtualHost>
<VirtualHost *:80>
ServerName example.com
ServerAlias www.example.com
Redirect permanent / https://example.com/
</VirtualHost>
This assumes mod_headers is enabled. If it isn’t, your config will look fine and still do nothing. I’ve seen that one more than once.
Common HSTS mistakes
Setting HSTS on HTTP responses
Browsers ignore it. HSTS must come from HTTPS.
Using includeSubDomains too early
This is the classic self-own. If any subdomain is not HTTPS-capable, users will get blocked.
Preloading before you’re ready
Getting onto the preload list is easy compared to getting off it. Treat it like a one-way door unless you have a very good reason.
Forgetting non-browser clients
Some API consumers, bots, embedded devices, or legacy clients may not behave like modern browsers. HSTS is mainly a browser control. Don’t assume every client upgrades requests the same way.
Assuming redirects are enough
They aren’t. Redirects happen after the insecure request has already been made.
HSTS and preload: the first-visit problem
Normal HSTS only works after the browser has seen your policy once. So what about the very first connection?
That’s where preload helps. Browsers like Chrome maintain a built-in HSTS preload list for domains that meet strict criteria. If your domain is on that list, the browser enforces HTTPS before any network request, even on first contact.
For a security-sensitive domain, preload is powerful. For a chaotic domain portfolio, it can be brutal.
My rule of thumb is simple: if your organization struggles to inventory subdomains, don’t preload yet.
How to verify your setup
Don’t just add the header and assume you’re done. Check the real response from production, including redirects, edge caches, and CDN behavior.
You want to verify:
- The HSTS header appears on HTTPS responses
- HTTP requests redirect to HTTPS
- The policy value is what you intended
- Subdomains behave consistently if
includeSubDomainsis used
Test your HSTS configuration and other security headers at headertest.com — free, instant, no signup required.
Also test manually with your browser’s dev tools and command-line requests through any CDN layers you use. Edge services sometimes strip or override headers in ways people don’t notice until later.
The bottom line
HSTS is one of those rare security controls that is simple, mature, and genuinely effective. It closes the gap between “we support HTTPS” and “browsers must use HTTPS.”
If your site is already on HTTPS, HSTS is the next obvious step. Start with a short max-age, validate everything, then ramp up to a year. Add includeSubDomains when you know your domain is clean. Add preload only when you’re ready to live with it.
That’s the practical version. No magic, no hype — just one header that makes downgrade attacks a lot harder and your HTTPS deployment actually stick.