Web servers
How to enable HSTS (HTTP Strict Transport Security)
Enable HSTS by sending a Strict-Transport-Security header on HTTPS responses, starting with max-age=300 and raising it in stages to one or two years. Browsers then refuse to load the site over plain HTTP. Add includeSubDomains and preload only after every subdomain serves HTTPS.
The examples cover Nginx, Apache httpd 2.4, Caddy 2 and IIS 10.
Prerequisites
HSTS removes the option to fall back to HTTP, so HTTPS must work everywhere first.
- A valid certificate with automatic renewal (see automatic renewal).
- An HTTP-to-HTTPS redirect in place (redirect HTTP to HTTPS).
- No mixed content and, if you plan
includeSubDomains, HTTPS on every subdomain.
Step 1: Add the header with a short max-age
Start with max-age=300, so a mistake locks browsers to HTTPS for five minutes only. Nginx, in the listen 443 ssl server block:
add_header Strict-Transport-Security "max-age=300" always;
add_header directives are inherited only if a location block defines none of its own; repeat the line in any location that sets other headers.
Apache, inside the *:443 virtual host (needs a2enmod headers):
Header always set Strict-Transport-Security "max-age=300"
Caddy, inside the site block:
header Strict-Transport-Security "max-age=300"
IIS, in the site's web.config:
<system.webServer>
<httpProtocol>
<customHeaders>
<add name="Strict-Transport-Security" value="max-age=300" />
</customHeaders>
</httpProtocol>
</system.webServer>
Reload the server after the change.
Step 2: Raise max-age in stages
Raise max-age one level at a time, and watch for problems for a few days at each level:
max-age=300 (5 minutes)
max-age=86400 (1 day)
max-age=604800 (1 week)
max-age=31536000 (1 year)
max-age=63072000 (2 years)
Step 3: Add includeSubDomains
includeSubDomains extends the policy to every subdomain, so add it only after each one serves HTTPS:
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains" always;
Step 4 (optional): Preload
Preloading builds the policy into browsers. To qualify, the apex domain must serve max-age=31536000 or more with includeSubDomains; preload, have a valid certificate, and redirect HTTP to HTTPS on the same host before redirecting elsewhere:
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
Then submit the domain at hstspreload.org. Read the removal section there first.
Verify
The live site should return the header with the max-age you set. Check it:
curl -sI https://example.com | grep -i strict-transport-security
The HSTS checker also reports preload eligibility. Confirm the certificate behind it with the SSL checker, or from any terminal:
curl ssl.sh/example.com
Automatic renewal
HSTS turns certificate problems into outages: when the certificate expires or does not match, browsers show an error with no "proceed anyway" option. That makes reliable renewal mandatory. Use an ACME client that runs at least daily and follows ARI, reload services after renewal, and monitor the live certificate with ssl.sh monitoring (in development). This matters more as lifetimes shrink: 200 days maximum since March 15, 2026, Let's Encrypt's default 64 days from February 10, 2027, and 47 days maximum from March 15, 2029.
Troubleshooting
- Header missing on some pages: an Nginx
locationwith its ownadd_headerdrops inherited headers, oralwaysis missing so error pages lack it. - Users cannot bypass a certificate error: that is HSTS working as designed; fix the certificate, see HSTS cannot bypass and NET::ERR_CERT_DATE_INVALID.
- An internal subdomain broke after includeSubDomains: give it a valid certificate, or lower max-age to 0 on the apex and wait for browsers to forget.
- Header sent over HTTP: harmless but ignored; browsers only accept HSTS over HTTPS.
- Preload submission rejected: the checker on hstspreload.org lists the failing requirement, often a missing redirect on the apex or a max-age below one year.
FAQ
How do I turn HSTS off again?
Send Strict-Transport-Security: max-age=0 over HTTPS. Each browser forgets the policy the next time it sees that header, so users who do not revisit keep the old policy until it expires. Preloaded domains must also request removal at hstspreload.org, which takes months to reach browser releases.
Is includeSubDomains safe?
Only if every subdomain, including internal ones like intranet.example.com or devices with their own web interface, serves valid HTTPS. Check with CT search and DNS before adding it.
Should I submit my domain to the preload list?
Only if you are sure the whole domain will stay HTTPS-only for years. Preloading protects the first visit, but removal is slow and affects every subdomain. Many sites get most of the benefit from a long max-age without preloading.