Nginx HSTS max-age — Best Practices and Common Mistakes
Updated April 2026
Reading this? Verify your fix live. Check your HSTS config → HeadersFixer
HSTS max-age is the most consequential number in your HTTP headers. Set it too high before you're ready and users are locked out if HTTPS breaks. Set it too low and it provides no protection between visits. Here is the right progression.
The correct Nginx HSTS config
server { listen 80; server_name yourapp.com www.yourapp.com; # Redirect HTTP to HTTPS — do NOT add HSTS here return 301 https://yourapp.com$request_uri;
}
server { listen 443 ssl http2; server_name yourapp.com; # HSTS goes only in the HTTPS block add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
}
The ramp-up progression
| Stage | max-age value | Duration | What to verify |
|---|---|---|---|
| 1. Testing | 86400 (1 day) | 1 week | HTTPS works, cert renews, no broken subdomains |
| 2. Stable | 2592000 (30 days) | 2 weeks | No HTTPS issues, no complaints |
| 3. Production | 31536000 (1 year) | Permanent | All good — this is your target |
| 4. Preload | 31536000 + preload flag | Submit to list | All subdomains HTTPS, includeSubDomains set |
Common mistakes
Mistake 1 — HSTS in the HTTP block
# Wrong — browsers ignore HSTS over HTTP
server { listen 80; add_header Strict-Transport-Security "max-age=31536000"; # ← ignored return 301 https://...;
}
Mistake 2 — max-age=0 thinking it disables HSTS
# max-age=0 tells the browser to DELETE its stored HSTS record # This is the correct way to remove HSTS — but you must send it over HTTPS # Sending max-age=0 over HTTP does nothing Strict-Transport-Security: max-age=0 # sent over HTTPS = removes HSTS # sent over HTTP = ignored
Mistake 3 — includeSubDomains before all subdomains are ready
# includeSubDomains applies HSTS to ALL subdomains # If staging.yourapp.com is HTTP-only, users cannot access it after HSTS # Check all subdomains support HTTPS before adding includeSubDomains: # dev.yourapp.com, staging.yourapp.com, api.yourapp.com, etc.
Check if you're on the preload list
# hstspreload.org — check your domain status # Requirements to be added: # 1. Valid HTTPS certificate # 2. All HTTP redirects to HTTPS # 3. max-age >= 31536000 (1 year) # 4. includeSubDomains present # 5. preload present in the header # Once on the preload list: # HTTPS is enforced even on first visit (before browser has seen your header) # This is PERMANENT — removal takes months to propagate from browsers
Verify your HSTS header
curl -I https://yourapp.com | grep -i strict-transport # Should return: # strict-transport-security: max-age=31536000; includeSubDomains; preload
Use HeadersFixer to scan your live site — it checks HSTS is present, sent over HTTPS only, and has a sufficient max-age value.
Check your HSTS config → HeadersFixer