HSTS Preload vs Manual Enforcement: Trade-offs
This guide compares the two ways to enforce HTTPS-only browsing: shipping a preload-eligible header and submitting the domain to the browser preload list, versus serving a long max-age and relying on each browser to learn the policy on first contact. The protocol mechanics and RFC 6797 threat model behind both approaches live in the HTTP Strict Transport Security (HSTS) deep dive; here the focus is the exact wire values, the operational cost of each path, and how to choose.
Configuration Syntax & Exact Values
The two strategies differ by one directive — preload — but that single token changes the rollback cost from days to months.
Preload-eligible header (qualifies for submission to the browser preload list):
Strict-Transport-Security: max-age=63072000; includeSubDomains; preload
Manual enforcement header (long lifetime, no list submission):
Strict-Transport-Security: max-age=63072000; includeSubDomains
max-age=63072000— enforcement lifetime in seconds (two years). The preload list requires at least31536000(one year); anything shorter is rejected byhstspreload.org. Both strategies use a long value, so the lifetime is not the deciding factor.includeSubDomains— extends the policy to every host below the apex (api.example.com,internal.example.com, hosts the browser has never visited). It is mandatory for preload submission, and optional but recommended for manual enforcement.preload— a valueless flag that signals consent to be hard-coded into the browser binary. The flag alone enforces nothing; the domain only gains list protection after you submit it athstspreload.organd a browser release ships it. Addingpreloadwithout submitting does nothing useful; submitting without serving the flag fails validation.
The defining difference: a manual policy is learned by each browser only after its first successful HTTPS response, so a brand-new visitor’s first request can still be downgraded. A preloaded domain is enforced before the browser ever connects, closing the first-visit gap — at the cost of near-permanent commitment.
Server-Side Configuration
Emit the header once, at the outermost layer that touches every response. The only on-server change between the two strategies is the presence of the preload token; the list submission happens separately at hstspreload.org.
Nginx
# Preload-eligible
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
# Manual enforcement (no list submission)
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains" always;
always forces emission on 4xx/5xx responses; without it the header vanishes from error pages and preload validation can fail. An add_header in a location block replaces inherited headers, so repeat the directive in any location that sets its own.
Apache
Header always set Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"
always appends the header even on internally generated error documents, which the default onsuccess table skips. Requires mod_headers (a2enmod headers). Drop the preload token for manual enforcement.
Cloudflare
Dashboard → SSL/TLS → Edge Certificates → HTTP Strict Transport Security (HSTS). Enable it, set Max-Age to 12 months or longer, toggle Include subdomains to On, and enable No-Sniff. To make the domain preload-eligible, also toggle Preload to On — Cloudflare then appends the preload token at the edge. Delete any duplicate origin add_header so only one copy reaches the browser.
Node/Helmet
const helmet = require('helmet');
// Preload-eligible
app.use(helmet.hsts({ maxAge: 63072000, includeSubDomains: true, preload: true }));
// Manual enforcement
app.use(helmet.hsts({ maxAge: 63072000, includeSubDomains: true }));
Register Helmet before route and error handlers so short-circuited responses still carry the header. Behind a TLS-terminating proxy, set app.set('trust proxy', 1) so Express treats forwarded requests as secure.
Diagnostic & Verification Steps
Confirm the exact wire value:
curl -sI https://yourdomain.com | grep -i strict-transport-security
Expected output (preload-eligible):
strict-transport-security: max-age=63072000; includeSubDomains; preload
Check preload eligibility before submitting. The official status tool reports every blocking condition:
curl -s "https://hstspreload.org/api/v2/status?domain=yourdomain.com"
Expected output once eligible:
{"name":"yourdomain.com","status":"preloaded"}
Before submission the same endpoint returns "status":"unknown"; a domain that has failed a re-check returns "status":"pending-removal". The web form at hstspreload.org lists each unmet requirement: a 301/302 from http:// to the HTTPS apex, the apex serving the preload token with max-age>=31536000 and includeSubDomains, and a valid certificate chain.
Verify subdomain coverage — preload enforces HTTPS on every subdomain, so any host without matching TLS becomes unreachable:
openssl s_client -connect internal.yourdomain.com:443 -servername internal.yourdomain.com < /dev/null 2>/dev/null \
| grep -E 'Verify return code'
Expected output: Verify return code: 0 (ok) on every subdomain, including internal-only hosts.
Browser DevTools: Network tab → filter Doc → select the document request → Response Headers → confirm the value. Cross-check the cached entry at chrome://net-internals/#hsts via Query HSTS/PKP domain; a preloaded domain shows static_sts_domain populated, a manually learned one shows dynamic_sts_domain.
Edge Cases, Security Implications & Safe Rollback
Preload is the only HTTPS hardening control that is genuinely hard to reverse, so the trade-offs are asymmetric.
- Preload removal takes months — the key risk. Removing a domain from the preload list is a request, not a switch: you serve
Strict-Transport-Security: max-age=0over HTTPS, submit a removal athstspreload.org, then wait for the next stable browser release to ship the change and for users to update. That is months, not minutes. A preloaded domain that loses its certificate or needs a plain-HTTP host is effectively locked until then. - Every subdomain must serve HTTPS — including internal ones. Because preload mandates
includeSubDomains, hosts the browser has never seen (monitoring.,legacy-api.,intranet.) are enforced immediately. Any one of them lacking a valid certificate producesNET::ERR_CERT_*errors with no proceed button. Audit the full DNS zone (subfinder+httpx, or a zone export) before submitting. - The first-visit gap without preload. Manual enforcement only protects a browser after its first successful HTTPS response is observed. A first-ever visit over
http://, or a visit after the cached policy expired, can still be SSL-stripped on a hostile network. Preload is the only way to close that gap.
Rollback directive. For manual enforcement, revert by serving Strict-Transport-Security: max-age=0; includeSubDomains over HTTPS; clients clear the policy on their next visit. For a preloaded domain, serving max-age=0 is necessary but not sufficient — you must also request removal at hstspreload.org and wait out the browser release cycle.
Decision Guidance
Pick preload for high-value origins — banking, identity providers, anything where a single SSL-strip on a first visit is unacceptable — provided you control TLS on every subdomain and have organizational sign-off on the irreversibility. Pick manual enforcement for sites with sprawling or partly-delegated subdomains, frequent infrastructure churn, or any host that may legitimately need plain HTTP, where a long max-age delivers most of the protection while remaining reversible in days.
Frequently Asked Questions
Does adding the preload token alone protect new visitors?
No. The token only signals eligibility. New visitors are protected before first contact only after you submit the domain at hstspreload.org and a browser release ships the entry. Until then the domain behaves like manual enforcement.
How long does it take to remove a domain from the preload list?
Months. You serve max-age=0, submit a removal request, and wait for the change to propagate through stable browser releases and user updates. There is no fast path, which is why preload should be treated as a near-permanent commitment.
What is the minimum max-age for preload?
31536000 (one year). The submission tool rejects shorter values. Most hardening baselines use 63072000 (two years) for both preload and manual policies.
Is manual enforcement vulnerable to SSL stripping?
Only on a browser’s very first contact, or after its cached policy expires. Once a browser has seen one valid HTTPS response, the policy is enforced for the full max-age. Preload is the only mechanism that also covers the first-ever request.
Conclusion
Whichever path you choose, prove it in stages first: serve the long max-age with includeSubDomains in staging, validate every subdomain over HTTPS, then run it in production for weeks. Only after that stable window should you add the preload token and submit to hstspreload.org — because that final step is the one you cannot quietly undo. If any subdomain or HTTP-only host might break, stay on manual enforcement.