What Is HSTS?
HTTP Strict Transport Security (HSTS) is a security mechanism that forces web browsers to only communicate with your website over HTTPS. Once a browser receives the HSTS header from your site, it will automatically convert all future HTTP requests to HTTPS — before even making a network request.
This eliminates an entire class of attacks known as SSL stripping, where an attacker downgrades a secure connection to plain HTTP and intercepts the traffic.
Without HSTS, here is what happens when someone types yoursite.com into their browser:
- 1Browser sends an HTTP request to
http://yoursite.com - 2Your server responds with a 301 redirect to
https://yoursite.com - 3Browser follows the redirect to HTTPS
The problem is step 1. That initial HTTP request travels in plaintext. An attacker on the same network (a coffee shop, airport, or compromised router) can intercept it and serve a fake version of your site — or simply read everything in transit.
With HSTS, the browser skips step 1 entirely. It knows your site only speaks HTTPS and upgrades the connection internally.
How HSTS Works
HSTS operates through a single HTTP response header:
Strict-Transport-Security: max-age=31536000; includeSubDomains; preloadWhen a browser receives this header over a valid HTTPS connection, it stores a record that says: "For the next max-age seconds, always use HTTPS for this domain."
The HSTS lifecycle
- 1First visit — User visits your site over HTTPS and receives the HSTS header
- 2Browser stores the policy — The domain is added to the browser's HSTS list
- 3Subsequent visits — Browser automatically upgrades HTTP to HTTPS (307 Internal Redirect)
- 4Expiry — After
max-ageseconds without a revisit, the policy expires
Key point: HSTS only activates after the first successful HTTPS visit. It cannot protect the very first connection — that is where HSTS preloading comes in (covered below).
The Strict-Transport-Security Header Explained
The header has three directives:
max-age (required)
The time in seconds that the browser should remember to only use HTTPS. Common values:
| Value | Duration | Use case |
|---|---|---|
300 | 5 minutes | Testing — start here |
86400 | 1 day | Initial rollout |
2592000 | 30 days | After verifying no issues |
31536000 | 1 year | Production standard |
63072000 | 2 years | Required for preloading |
Start with a low max-age (300 seconds) and increase it gradually. If you misconfigure HSTS with a long max-age, visitors will be unable to access your site over HTTP for the full duration — even if you remove the header.
includeSubDomains (optional but recommended)
Applies the HSTS policy to all subdomains. Without it, app.yoursite.com or api.yoursite.com would still be vulnerable to downgrade attacks.
Warning: Only enable this if ALL subdomains support HTTPS. If legacy.yoursite.com does not have a valid certificate, it will become inaccessible.
preload (optional)
Signals to browser vendors that you want your domain added to the HSTS preload list — a hardcoded list of HTTPS-only domains shipped with browsers. This eliminates the "first visit" vulnerability.
Setting Up HSTS
Nginx
Add the header to your server block:
server {
listen 443 ssl http2;
server_name yoursite.com;
# HSTS — start with 5 minutes for testing
add_header Strict-Transport-Security "max-age=300; includeSubDomains" always;
# After testing, increase to 1 year:
# add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
ssl_certificate /etc/ssl/certs/yoursite.crt;
ssl_certificate_key /etc/ssl/private/yoursite.key;
# ... rest of your config
}
# Redirect HTTP to HTTPS
server {
listen 80;
server_name yoursite.com;
return 301 https://$host$request_uri;
}The always keyword ensures the header is sent even on error responses (404, 500, etc.).
Test and reload:
nginx -t && systemctl reload nginxApache
Enable mod_headers and add the header:
<VirtualHost *:443>
ServerName yoursite.com
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
SSLEngine on
SSLCertificateFile /etc/ssl/certs/yoursite.crt
SSLCertificateKeyFile /etc/ssl/private/yoursite.key
</VirtualHost>
# Redirect HTTP to HTTPS
<VirtualHost *:80>
ServerName yoursite.com
Redirect permanent / https://yoursite.com/
</VirtualHost>Enable the module and restart:
a2enmod headers
systemctl restart apache2Cloudflare
Cloudflare supports HSTS natively:
- 1Go to SSL/TLS → Edge Certificates
- 2Scroll to HTTP Strict Transport Security (HSTS)
- 3Click Enable HSTS
- 4Configure: max-age = 12 months, includeSubDomains = On, Preload = On
- 5Click Save
Cloudflare adds the header at the edge, so you do not need to modify your origin server.
Next.js
Add the header in next.config.js:
/** @type {import('next').NextConfig} */
const nextConfig = {
async headers() {
return [
{
source: '/(.*)',
headers: [
{
key: 'Strict-Transport-Security',
value: 'max-age=31536000; includeSubDomains; preload',
},
],
},
];
},
};
module.exports = nextConfig;If you deploy on Vercel, the header is applied at the edge. For self-hosted Next.js, ensure your reverse proxy also handles HTTPS termination.
HSTS Preloading
The HSTS preload list is maintained at hstspreload.org. Once your domain is on this list, browsers will enforce HTTPS from the very first visit — no initial HTTP request needed.
Requirements for preloading
1. Serve a valid SSL certificate
2. Redirect all HTTP traffic to HTTPS
3. Serve the HSTS header on the base domain with:
- max-age of at least 2 years (63072000 seconds)
- includeSubDomains directive
- preload directive
4. All subdomains must support HTTPS
How to submit
- 1Verify your site meets all requirements
- 2Go to hstspreload.org
- 3Enter your domain and submit
Warning: Preloading is effectively permanent. Removing your domain from the preload list takes months and requires a browser update cycle. Only preload when you are fully committed to HTTPS-only.
Common Mistakes
1. Setting HSTS on HTTP responses
HSTS headers sent over HTTP are ignored by browsers (by design — an attacker could inject the header on an insecure connection). Only set the header on HTTPS responses.
2. Starting with a long max-age
If something breaks (a subdomain without HTTPS, a misconfigured certificate), users will be locked out for the entire max-age duration. Start with 5 minutes, then 1 day, then 30 days, then 1 year.
3. Enabling includeSubDomains without checking all subdomains
Every subdomain must have a valid HTTPS configuration. Audit all of them before enabling this directive:
# Check subdomains
curl -sI https://www.yoursite.com | grep -i strict
curl -sI https://app.yoursite.com | grep -i strict
curl -sI https://api.yoursite.com | grep -i strict4. Forgetting to renew your SSL certificate
With HSTS active and your SSL certificate expired, visitors cannot access your site at all — the browser will not allow a fallback to HTTP. Set up automated certificate renewal:
# Let's Encrypt auto-renewal
certbot renew --dry-run5. Not adding the always flag in Nginx
Without always, Nginx only sends the HSTS header on 2xx and 3xx responses. Error pages will not carry the header.
Verify Your HSTS Config
Command line
curl -sI https://yoursite.com | grep -i strict-transport-securityExpected output:
strict-transport-security: max-age=31536000; includeSubDomains; preloadBrowser DevTools
- 1Open DevTools (F12)
- 2Go to the Network tab
- 3Reload the page
- 4Click the first request (your domain)
- 5Look for
Strict-Transport-Securityin the response headers
Automated scanning
Run a scan at zeriflow.com to check your HSTS configuration alongside all other security headers. The scanner verifies not just the presence of the header but also whether the max-age is sufficient and whether includeSubDomains is set.
A properly configured HSTS header is one of the highest-impact security improvements you can make — and it takes less than 5 minutes to set up.
