HSTS, short for HTTP Strict Transport Security, is an HTTP response header that tells the browser: 'For the next year, request this site only over HTTPS, even if the user typed http on purpose'. It is a critical layer above the standard 301 redirect - not a replacement, but a complement.
Why this matters
Forcing HTTPS via a 301 redirect alone leaves a vulnerability window on the very first request. Picture this scenario: a user at a coffee shop joins the cafe Wi-Fi and types 'example.com' in the address bar. The browser sends an HTTP request. An attacker on the same Wi-Fi (an SSL-stripping attack) intercepts, returns your page without a redirect to HTTPS, and keeps the session on HTTP - so they can read login cookies. With HSTS, if the user has visited the site at least once before, the browser remembers the directive and never even sends the HTTP request - it goes straight to HTTPS, denying the attacker any window. A user with a stale http:// bookmark is also protected: the next click silently upgrades. HSTS also prevents the user from clicking through a certificate warning - once HSTS is active, the browser refuses to bypass.
How to detect
The audit issues a HEAD against the homepage and reads the Strict-Transport-Security header. Missing = red. Even if present, max-age below 2592000 (30 days) is considered weak. Manually:
curl -I https://example.com | grep -i strict should return something like: 'Strict-Transport-Security: max-age=31536000; includeSubDomains'. securityheaders.com gives a graded report.How to fix
- Prerequisite: verify the site has been on stable HTTPS for at least two weeks with no mixed-content issues. Enabling HSTS on a fragile site causes the browser to block resources still served over HTTP.
- Start with a short max-age to confirm: 300 seconds (5 minutes). Add the header.
- Verify it appears in curl. Verify the site still works.
- Then raise to 31536000 (one year).
- Apache in .htaccess at the document root:
<IfModule mod_headers.c> Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains" </IfModule> - Nginx inside the server block:
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; - Add includeSubDomains only when you are sure every subdomain is also HTTPS. If blog.example.com still runs HTTP, it will break.
- Verify the header is sent: curl -I https://example.com | grep -i strict.
- Consider submitting to the Chrome HSTS preload list at hstspreload.org - that requires the preload directive in the header. This is nearly irreversible: removal from the preload list takes months.
Common mistakes
Do not enable HSTS before HTTPS is stable - browsers will block the site until max-age expires, even if you remove the header. Do not enable preload on day one - it requires 1) max-age >= 31536000, 2) includeSubDomains, 3) the preload directive, 4) HTTPS on every subdomain. Do not enable includeSubDomains without verifying every subdomain is ready. Do not end the value with a stray colon - bad syntax burns hours of debugging.
Verifying the fix
Run curl -I https://example.com - the header should be present. Test on securityheaders.com - your grade should rise to A or A+. Open http://example.com - the browser should upgrade to HTTPS before the request even reaches the server (Chrome DevTools shows 'HSTS internal redirect').