HSTS max-age shorter than one year
What is this issue?
The page sends a Strict-Transport-Security (HSTS) header, but its max-age
is shorter than one year (31,536,000 seconds).
HSTS tells a browser: "for the next max-age seconds, only ever connect to this
site over HTTPS." The browser remembers that instruction, and refreshes it on
every visit. A short max-age means the browser forgets quickly — anyone who
has not visited for a day, an hour, or a few minutes is unprotected again.
For a page to pass this check:
max-ageis at least31536000.
Strict-Transport-Security: max-age=31536000; includeSubDomainsA short value is common during a careful rollout (start at 5 minutes, then a week, then a month). That is good practice — as long as the rollout is finished. This check reports a rollout that stopped part way.
When this check stays silent
- Plain HTTP pages. Browsers ignore HSTS sent over HTTP (RFC 6797 §8.1), and the page is already reported as not served over HTTPS.
- No usable policy. A missing header, a header with no
max-age, andmax-age=0are all reported by "No Strict-Transport-Security header" (#41). This check only looks at policies that browsers would actually apply, so the two never describe the same header.
Why it matters
The protection lapses between visits. HSTS only helps a visitor whose browser still remembers the policy. With
max-age=86400, a customer who comes back after two days typesexample.com, the browser sends a plain-HTTP request first, and that request can be intercepted and redirected (an "SSL stripping" attack) before the site's own redirect to HTTPS arrives.One year is the accepted baseline. It is the minimum the browser preload list accepts, the value recommended by OWASP and Mozilla, and what security scanners and audits expect.
Trust signals. A site that only half-enables HSTS is easy to spot in security reviews and vendor questionnaires.
Effect on the health score
This is a standard issue. It deducts from the Delivery & Trust score for each affected page.
How to fix it
Confirm the whole site works over HTTPS, including every page and asset, before committing browsers to HTTPS for a year.
Raise
max-ageto at least one year. Two years (63072000) is also common and is what the preload list recommends.nginx
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;Apache
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"Cloudflare: SSL/TLS → Edge Certificates → HTTP Strict Transport Security, set Max Age to 12 months.
If you are mid-rollout, keep stepping up (one week, one month) but plan the final step to one year rather than leaving a short value in place.
Check every layer. If the origin sends one value and the CDN another, the browser sees whichever arrives first. Look at the header a browser receives:
curl -sI https://example.com | grep -i strict-transport
Examples
Example 1: A one-day policy
Strict-Transport-Security: max-age=86400; includeSubDomainsReported: 86,400 seconds is one day.
Example 2: A one-year policy
Strict-Transport-Security: max-age=31536000Passes this check. (It may still be reported for missing
includeSubDomains, which is a separate suggestion.)
Example 3: A quoted, upper-case directive
Strict-Transport-Security: MAX-AGE="600"Reported: directive names ignore case and values may be quoted, so this is read as a 10-minute policy.
Example 4: max-age=0
Strict-Transport-Security: max-age=0Not this issue: max-age=0 switches HSTS off and is reported as "No
Strict-Transport-Security header" instead.
How PixyScan detects this
Only HTTPS pages are checked, because browsers ignore HSTS over HTTP.
Skips anything #41 already reports. No header, no
max-age, ormax-age=0are the "No Strict-Transport-Security header" issue.Parses the policy as described below.
Reports when
max-ageis below 31,536,000 seconds. Exactly one year passes. The finding records the value found and the value required.Runs on every page, because the header is set per response. A CDN or proxy that sends a weaker policy on some paths than others shows up on exactly those pages.
How the header is read
PixyScan reads the header the way RFC 6797 tells a browser to:
- Only the first header field counts. If the server sends the header twice, browsers use the first one and ignore the rest, and so does this check.
- Directive names ignore case.
includeSubDomains,INCLUDESUBDOMAINSandincludesubdomainsare the same directive. - Values may be quoted.
max-age="31536000"is valid. - A repeated directive makes the header invalid. Browsers ignore a header
like
max-age=600; max-age=700entirely, so this check says nothing about it rather than giving advice about a policy no browser applies.
What we store
Storage Level
Page Level
Database Table / Prisma Model
PageSecurityHeader, and the finding on audit_issues.details
Fields Used
| Field | Type | Description |
|---|---|---|
| page_security_headers.hsts_header | String | The Strict-Transport-Security header exactly as the page sent it |
Stored Fields on the finding
| Field | Type | Description |
|---|---|---|
| message | String | What is missing from the policy and why it matters |
| hstsHeader | String | The header as sent |
| maxAge | Int | The max-age found, in seconds |
| requiredMaxAge | Int | 31536000, the value required |
Detection Dependencies
- HTTP Response headers
- HTTPS (the check only applies to pages served over HTTPS)