How to Remove HSTS and Roll Back Preload
This guide is part of the HTTP Strict Transport Security (HSTS) Deep Dive reference. It covers the one path that reverses HSTS safely: serving max-age=0 to expire cached policy, then — only if you ever submitted the domain — dropping the preload token and filing removal at hstspreload.org. Deleting the header does nothing; a browser that already cached your policy keeps enforcing HTTPS until the countdown reaches zero. Get the order wrong and you strand every returning visitor on an unreachable HTTPS endpoint with no in-browser override.
Configuration Syntax & Exact Values
Rolling back is not “remove the line.” It is “serve a header that overwrites the cached policy with an expired one.” The wire value that does this is a max-age of zero.
Strict-Transport-Security: max-age=0; includeSubDomains
max-age=0— the retraction signal. Per RFC 6797 §6.1.1, amax-ageof0instructs the browser to delete the stored HSTS entry for the host on receipt of this header over HTTPS. Any other value refreshes and extends enforcement.includeSubDomains— keep this flag during rollback if your live policy carried it. The zeroing header must match the scope of the policy it retracts; a browser that cachedincludeSubDomainsneeds a zeroing header that also assertsincludeSubDomainsto clear subdomain entries cleanly. Drop it only after clients have had time to see the zero.- No
preloadtoken. Removingpreloadfrom the header is a prerequisite for hstspreload.org removal (the list validator rejects domains still advertising the token), but on its own it changes nothing for browsers that shipped your domain in their built-in list.
Two facts govern everything below. First, the zeroing header is only honoured over HTTPS — a browser under active HSTS never issues a plaintext request, so you must keep TLS working to deliver the retraction. Second, max-age=0 reaches only clients that revisit while you serve it; a visitor who never returns during the rollback window keeps the old countdown.
Server-Side Configuration
Swap the enforcing value for max-age=0 at the same layer that currently emits the header. If both a CDN and origin emit HSTS, zero it at the edge and delete the origin copy — otherwise the origin’s non-zero value overwrites the edge’s zero for any request that reaches it. Keep TLS certificates valid and HTTPS listening for the entire rollback window; a broken cert during rollback locks out every enforcing client.
Nginx
add_header Strict-Transport-Security "max-age=0; includeSubDomains" always;
always forces the header onto 4xx/5xx responses too; a client hitting an error page still receives the retraction. An add_header inside a location block replaces inherited headers, so repeat this line in every location that sets its own HSTS.
Apache
Header always set Strict-Transport-Security "max-age=0; includeSubDomains"
always emits the header even on internally generated error documents that the default onsuccess table skips. Requires mod_headers (a2enmod headers).
Cloudflare
Dashboard → SSL/TLS → Edge Certificates → HTTP Strict Transport Security (HSTS). Set Max-Age to 0 (the dashboard exposes a “No max-age / 0” option) and leave Include subdomains matching your prior setting, then Save. Cloudflare rewrites the header at the edge on all proxied responses. Delete any duplicate origin add_header so the edge zero is the only value that reaches the browser. This is the Cloudflare header management path.
Node / Helmet
const helmet = require('helmet');
app.use(helmet.hsts({ maxAge: 0, includeSubDomains: true }));
Register Helmet before route and error handlers so short-circuited responses still carry the zeroing header. Behind a TLS-terminating proxy, keep app.set('trust proxy', 1) so Express still treats requests as secure and Helmet emits the header.
Django / FastAPI
# Django settings.py
SECURE_HSTS_SECONDS = 0
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
SECURE_HSTS_PRELOAD = False
Django’s SecurityMiddleware emits max-age=0 when SECURE_HSTS_SECONDS is 0 and still appends includeSubDomains, which is exactly the retraction scope you want. Setting SECURE_HSTS_PRELOAD = False strips the preload token — necessary before hstspreload.org will accept a removal request. The same middleware pattern applies to FastAPI and Django hardening.
Diagnostic & Verification Steps
Confirm the origin now serves the zero:
curl -sI https://yourdomain.com | grep -i strict-transport-security
Expected output:
strict-transport-security: max-age=0; includeSubDomains
If you see a non-zero max-age, a caching layer or a second add_header is overwriting your change — resolve the duplicate before proceeding.
Verify no CDN/origin duplication (two headers means one may still enforce):
curl -sI https://yourdomain.com | grep -ci strict-transport-security
Expected output: 1 — a 2 means both edge and origin emit HSTS and you must remove one.
Confirm you have dropped the preload token (mandatory before submitting removal):
curl -sI https://yourdomain.com | grep -io 'preload'
Expected output: empty (no match). Any preload echo means hstspreload.org will reject the removal form.
Clear a single browser’s cached entry manually (for local testing). In Chrome, open chrome://net-internals/#hsts, enter the domain under Delete domain security policies, and click Delete. Then reload over http:// and confirm the browser no longer auto-upgrades:
curl -sI http://yourdomain.com | grep -i location
Expected output after clearing: no forced Location: https://… redirect originating from the browser’s own HSTS upgrade (server-side redirects, if you keep them, still appear — those are your choice, not HSTS).
Edge Cases, Security Implications & Safe Rollback
Three traps define this scenario. Each stems from the fact that HSTS state lives in the client, not on your server.
Trap 1 — the irreversibility of preload. Adding preload and submitting to hstspreload.org ships your domain in the source code of Chromium, Firefox, Safari and Edge. Removing it is not a config change; it is a request to edit those browsers’ bundled lists, which then propagate only when users install a new browser release. Removal takes months to reach the installed base, and older or un-updated browsers may enforce indefinitely. Treat preload submission as a near-permanent commitment; the full trade-off analysis lives in HSTS preload vs manual enforcement.
Trap 2 — deleting the header instead of zeroing it. Removing the add_header line leaves every previously-visited browser enforcing HTTPS until its own cached max-age expires — potentially two years. During that window a broken certificate or a decommissioned HTTPS listener is a hard lockout with no click-through (includeSubDomains extends the lockout to subdomains that never even served the header). Always serve max-age=0 for at least as long as your prior max-age, then remove the header only after the window closes.
Trap 3 — dropping HTTPS before clients see the zero. The retraction is delivered only over HTTPS. If you tear down TLS to “go back to HTTP,” enforcing browsers refuse the plaintext connection and never receive max-age=0, permanently stranding themselves. Keep the certificate valid and port 443 answering throughout.
The correct rollback order:
The interaction with framing and origin-isolation policies is unaffected — HSTS governs only the transport, so headers like X-Frame-Options and COOP/COEP keep working through a rollback. If you are decommissioning the domain entirely, pair the HSTS zero with Clear-Site-Data to flush other cached state.
Conclusion
Test the zeroing header in staging first: confirm curl returns exactly one max-age=0 header with the correct subdomain scope and no preload token. Then serve max-age=0 in production for at least as long as your old max-age — keeping TLS live throughout — so returning clients expire their cached policy. Only after that window, and only if you ever submitted to the list, drop the preload token, file removal at hstspreload.org, and accept that shipped browsers will keep enforcing for months until their bundled lists update.
Frequently Asked Questions
Does deleting the Strict-Transport-Security header remove HSTS?
No. Browsers that already cached your policy keep enforcing HTTPS until their stored max-age expires, regardless of whether the header is still present. You must actively serve max-age=0 over HTTPS so returning clients overwrite the cached entry with an expired one.
How long does HSTS preload removal take to reach users? Months. Removal from hstspreload.org edits the list bundled in browser source code, which only reaches users when they install a browser release built after the change. Clients that do not update may enforce your old preload policy indefinitely.
Should I keep includeSubDomains in the max-age=0 header?
Yes, if your live policy carried it. The zeroing header must match the scope of the policy it retracts so the browser clears subdomain entries as well as the apex. Drop includeSubDomains only after clients have had time to see the zero.
Why does the max-age=0 header have to be served over HTTPS? A browser under active HSTS never issues a plaintext request to your host — it auto-upgrades every request to HTTPS before sending it. The retraction can therefore only be delivered over a working TLS connection, so port 443 and a valid certificate must stay up for the entire rollback.
Can I remove the preload token but keep HSTS enforcement?
Yes. Serve a non-zero max-age without the preload token — for example max-age=63072000; includeSubDomains. This preserves normal cached HSTS enforcement while making the domain eligible for removal from the preload list, which is the safe middle ground between full enforcement and full rollback.