How to add a missing HSTS header on Apache, Nginx, and IIS
The hsts_missing advisory fires when an endpoint responds over HTTPS but does not send a Strict-Transport-Security (HSTS) header. HSTS instructs the browser to refuse plain-HTTP connections to your site for a set duration — defending against downgrade attacks and accidental http:// links.
What triggers the advisory
CertTracker issues an HTTP GET request alongside the TLS handshake and inspects the response headers for a Strict-Transport-Security header. If the header is missing entirely, the advisory fires.
(If the header is present but its max-age is below your company's threshold, the separate hsts_short advisory fires instead.)
Why it matters
Without HSTS, the first time a user types your-host.example.com into the address bar — no scheme — the browser sends an HTTP request. If your server redirects to HTTPS, that initial HTTP request can be intercepted on the user's network and silently downgraded by an attacker. HSTS prevents this once the browser has seen the header at least once.
For sites that handle anything sensitive — auth, customer data, payments — HSTS is essentially mandatory.
Recommended header value
Strict-Transport-Security: max-age=63072000; includeSubDomains; preload
Three components:
max-age=63072000— 2 years in seconds. Long enough to be meaningful, short enough that you can recover if you ever need to disable HTTPS on a sub-resource. (The current production CertTracker site uses this exact value — visible if youcurl -sI https://certtracker.12squared.com/.)includeSubDomains— applies HSTS to every subdomain too. Only add this if every subdomain genuinely serves HTTPS; otherwise you'll break the ones that don't.preload— opt-in to the HSTS preload list maintained by Google. Once you're on the list, browsers enforce HSTS on the very first visit, before they've ever seen the header. Don't addpreloaduntil you're sure you want it forever — removal from the list takes months.
A safer starter value if you're not ready to commit:
Strict-Transport-Security: max-age=31536000
One year, no subdomains, no preload. You can tighten later once you've verified nothing breaks.
Fix on Apache (2.4+)
In your vhost or ssl.conf:
<IfModule mod_headers.c>
Header always set Strict-Transport-Security "max-age=63072000; includeSubDomains"
</IfModule>
The always keyword ensures the header is emitted on error responses too (404s, 500s). Without it, some error paths emit responses without the header. Reload:
sudo a2enmod headers # Debian/Ubuntu, if not already loaded
sudo apachectl configtest && sudo systemctl reload apache2
Fix on Nginx (1.1.0+)
In your server { ... } block:
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains" always;
The always keyword is similarly important on Nginx — without it, add_header only emits on 2xx/3xx responses.
Reload:
sudo nginx -t && sudo systemctl reload nginx
Fix on IIS
In IIS Manager → site → HTTP Response Headers → Add. Name: Strict-Transport-Security, Value: max-age=63072000; includeSubDomains.
Or via web.config:
<configuration>
<system.webServer>
<httpProtocol>
<customHeaders>
<add name="Strict-Transport-Security" value="max-age=63072000; includeSubDomains" />
</customHeaders>
</httpProtocol>
</system.webServer>
</configuration>
Or for the entire server, in PowerShell as administrator (IIS 10+):
Add-WebConfigurationProperty -pspath 'MACHINE/WEBROOT/APPHOST' -filter "system.webServer/httpProtocol/customHeaders" -name "." -value @{name='Strict-Transport-Security';value='max-age=63072000; includeSubDomains'}
No restart required for web.config changes — IIS picks them up on the next request.
Fix on HAProxy
In your backend or frontend block:
http-response set-header Strict-Transport-Security "max-age=63072000; includeSubDomains"
Or only when responding over HTTPS:
http-response set-header Strict-Transport-Security "max-age=63072000; includeSubDomains" if { ssl_fc }
Reload:
sudo systemctl reload haproxy
Cloud and managed-service notes
- AWS CloudFront — add an Origin Response function or use the Response headers policies feature to inject the header at the edge. CloudFront supports HSTS as a built-in managed policy:
SimpleCORSandSecurityHeadersPolicy. - Azure App Service — Application Settings → can be added via web.config, or via the TLS/SSL settings blade under "HTTP Headers."
- Cloudflare — Dashboard → SSL/TLS → Edge Certificates → HTTP Strict Transport Security (HSTS) → Enable. UI walks you through max-age, subdomains, and preload.
A note before adding includeSubDomains and preload
Both flags have failure modes that are hard to undo:
includeSubDomainswill break any subdomain that's HTTP-only. Audit every subdomain first.preloadis even more committing — once you're on the Chrome preload list, removal takes months and your users can't bypass HSTS in the interim. Don't add this header on a site you're still building.
The HSTS preload submission site (hstspreload.org) explains the prerequisites and the irreversibility in plain language. Read it before submitting.
Verify the fix
curl -sI https://your-host.example.com/ | grep -i strict-transport-security
You should see something like:
Strict-Transport-Security: max-age=63072000; includeSubDomains
Trigger a re-scan from the CertTracker UI. The fastest path: open the monitor's detail modal (click the monitor row on the Monitors page), scroll to the Active Advisories section, and click the orange ⟳ Re-scan button on the right side of the section header. The status line below the header will confirm the cert re-poll and TLS audit are queued; the next 5-minute poller tick (within ~5 minutes) runs both, and the advisory clears if your fix took. See Where to find these buttons in the UI for visual landmarks.
Related advisories
- HSTS max-age too short — fires when the header is present but its max-age is below your company's configured threshold.
Back to the TLS hardening hub.