HSTS (HTTP Strict Transport Security) is a header that instructs the browser to always use HTTPS for your domain, even if the user types http://. It protects against SSL-stripping attacks and accidental navigation over HTTP.
How HSTS works
- The browser visits the site over HTTPS for the first time
- It receives the
Strict-Transport-Securityheader - It stores the domain in the HSTS list for the max-age period
- On the next visit — it automatically opens HTTPS without a redirect from the server
Configuring via .htaccess
<IfModule mod_headers.c>
# Basic HSTS: 1 year
Header always set Strict-Transport-Security "max-age=31536000"
# With subdomains included
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"
# With preload (to be included in the browser list)
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
</IfModule>
Configuring via Nginx
server {
listen 443 ssl;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
}
max-age=300 (5 minutes) for testing. Switch to 31536000 only after you've confirmed HTTPS works perfectly on all subdomains.HSTS Preload
The preload directive lets you add your domain to the built-in browser lists. This means HTTPS will be enforced even on the first visit (before the header is received):
- Submit a request at: hstspreload.org
- Requirements: max-age of at least 1 year, includeSubDomains, valid HTTPS on subdomains
Checking HSTS
# curl
curl -I https://example.com | grep Strict
# Or Chrome DevTools:
# Network → your request → Response Headers → Strict-Transport-Security
Common HSTS mistakes explained
NET::ERR_CERT_AUTHORITY_INVALIDwith no "Proceed anyway" button — on an HSTS domain, the browser will not let you bypass a certificate error the way it would on an ordinary site. If the certificate expires or was issued for a different domain, the site becomes completely unreachable, not just "unsafe."net::ERR_SSL_PROTOCOL_ERRORon a subdomain withincludeSubDomainsenabled — if even one subdomain lacks working HTTPS, the browser still forces a connection to it over HTTPS and gets a connection error instead of a soft fallback to HTTP.net::ERR_TOO_MANY_REDIRECTS— a typical result when the HTTPS redirect is configured both on a CDN or load balancer and on the origin server at the same time: the two redirects loop into each other.- A
Strict-Transport-Securityheader sent over a plain HTTP connection must be ignored by the browser per the specification — if the config sends it on both http and https, the very first visit still gets no protection without being on the preload list. - The
chrome://net-internals/#hstspage — Chrome's built-in tool for checking and manually deleting a browser's local HSTS entry for a specific domain, without waiting formax-ageto expire.
max-age values: what to set and when
| max-age value | Duration | When to use it | Risk |
|---|---|---|---|
| 300 | 5 minutes | Testing the setup against real traffic | Minimal — rolls back almost instantly |
| 86400 | 1 day | The first production stage after a successful test | A mistake can still be fixed within a day |
| 2592000 | 30 days | Stable HTTPS with no complaints | The certificate must stay valid for the entire period with no downtime |
| 31536000 | 1 year | Final stage, a mandatory requirement for preload | No rollback for visitors with an already-cached entry until it expires |
When it is too early for HSTS
- The site still serves part of its traffic over HTTP for legacy integrations — old payment gateways or IoT devices with no TLS support — a long
max-ageHSTS policy makes those integrations unreachable for every browser client at once. - Subdomains are issued to users automatically, such as a SaaS product on a wildcard domain, and not everyone is guaranteed a certificate right away —
includeSubDomainswill block access to subdomains that are not ready yet. - The domain is used for local development or a test environment with a self-signed certificate — a long HSTS duration turns every further certificate experiment into a chore of manually clearing settings in each browser.
- There is a chance the domain will need to go back to HTTP temporarily — during a migration or a CDN change without TLS termination — the preload list is edited slowly, removal from it takes months, and until then no browser will open the site over HTTP at all.
Verifying that HSTS is really protecting the site
# Check the header and its value
curl -sI https://example.com | grep -i strict-transport-security
# Confirm the HTTP version of the same domain still redirects to HTTPS
curl -sI http://example.com | grep -i location
# In Chrome: chrome://net-internals/#hsts → Query domain → enter the domain
A static_sts_domain field in the query result means the domain is baked into the browser's preload list, not just stored in the local cache after a first visit. These are different states: a cache entry disappears on its own once max-age expires, a preload-list entry does not.