HSTS max-age is shorter than six months
What this check looks for
The instruction to always use HTTPS expires sooner than it should. A visitor who has not been back for a while makes an insecure first request again.
Why it matters
The protection lasts exactly as long as max-age says. A short lifetime means the window where a visitor can be intercepted reopens regularly, and it is the one condition SSL Labs requires for its own top grade.
When the check passes, your report says: “The HSTS instruction lasts six months or more”.
What it costs your score
When this check fails it removes 8 points from your Web security score, before the status, confidence and repeat multipliers are applied. Web security carries a weight of 8 in the overall score.
It shares the web-security.hsts family ceiling of 28 points: however many findings that family produces, together they cannot remove more than that from Web security. One underlying problem showing up in several places is still one problem.
- Severity
- medium
- Default confidence
- confirmed
- Status when triggered
- warn
- Deduction
- 8 points
- Family cap
- web-security.hsts · 28
- Category
- Web security
- Module
- Web security headers
- Fix owned by
- user
- In the ruleset since
- 2026.09
How to fix it
Raise max-age to a year — 31536000 seconds.
The protection lasts only as long as max-age; a short one keeps reopening the gap.
Confirm HTTPS works on every hostname the policy will cover, including any subdomain
includeSubDomainswill reach. This is the step that matters: a hostname that cannot serve HTTPS becomes unreachable for the length of the max-age, and there is no way to reach the visitors who already have the policy.Set
max-age=31536000. That is a year, and it is also the preload list's minimum, so it leaves that option open rather than requiring a second change later.Keep the value stable. A browser refreshes the timer on every visit, so the policy is only as long as the last value you served.
Lower it before you remove it, if you ever need to: serving
max-age=0over HTTPS is what clears the policy, and it only reaches visitors who come back.
How to confirm it worked
curl -sSI https://‹host›/ | grep -i strict-transport-security
Strict-Transport-Security: max-age=31536000; includeSubDomainsA named slot like ‹domain› — and the braces left in the configuration below — is filled in with your own values when this rule appears on a report.
Remediation by platform
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;<IfModule mod_headers.c>
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"
</IfModule>header {
Strict-Transport-Security "max-age=31536000; includeSubDomains"
}Rules → Overview → Create rule → Response Header Transform Rule → Set static → Strict-Transport-Security = max-age=31536000; includeSubDomains
(Dashboard path as at 2026-09; Cloudflare reorganised these pages and may again.)The
alwaysflag matters: without it nginx omits the header on error responses, which are exactly the ones an attacker aims for. Note also that anyadd_headerin alocationblock discards everyadd_headerinherited from the server block.Shown because the response identified Apache. Put it in the virtual host rather than .htaccess: .htaccess is re-read on every request, and
Header always setthere runs too late for responses the server generates itself.A Transform Rule adds the header at Cloudflare's edge, so it applies only to responses that reach visitors through Cloudflare. An origin that is reachable directly — by IP, or through a DNS record that is not proxied — still serves the response measured here without it. Setting it at the origin covers both paths.
Technical detail
max-age=‹max age›. **Our floor is ‹minimum› seconds (six months), and it is ours** — a rollout threshold we chose, not a requirement anybody publishes. A short value is a reasonable *rollout* step and is the right way to start; it is not a finished configuration, and raising it is a one-line change once you are confident every hostname serves HTTPS correctly.
The preload list is stricter and is a separate bar: hstspreload.org requires max-age of at least **31536000** seconds — one year — together with includeSubDomains and preload. A lower figure was accepted once: the minimum was 18 weeks until Chromium raised it to a year in October 2017, and the shorter value now survives only for domains admitted in that era. So six months satisfies us and does not satisfy the preload list, which is why the recommendation below goes straight to a year.
Standards and references
Test this on your domain
Run the check that produces this finding, on its own, against any domain.
Other web security headers checks
- No Cross-Origin-Embedder-Policy is set
- No Cross-Origin-Opener-Policy is set
- No Cross-Origin-Resource-Policy is set
- No Content Security Policy is enforced
- The policy leaves plugins or the base URL unrestricted
- The Content Security Policy is report-only
- The policy allows eval()
- The policy allows inline scripts or styles
- The policy allows resources from anywhere
- A security header was sent twice with different values
- HTTP Strict Transport Security is not in force
- HSTS does not cover subdomains