You fixed your SSL certificate, or maybe you just let it expire on a test subdomain, and now the browser won't even try to load the page over HTTP anymore — it silently rewrites the URL to HTTPS and then shows a certificate error before your server gets a chance to respond. Clearing cookies doesn't help. Incognito mode doesn't help. That's HSTS, and once a browser has cached it for your domain, the browser — not your server — is the one enforcing HTTPS.

HSTS (HTTP Strict Transport Security) is a good security feature that occasionally turns into a self-inflicted outage. Here's what it actually does, why it locks people out, and how to turn it on (or fix it) without stranding yourself.

Symptom: What HSTS Lockout Looks Like

  • You type http://example.com and the browser instantly shows https://example.com in the address bar — no request ever goes to your server.
  • After removing SSL, switching hosts, or fixing Cloudflare's SSL mode, the site still throws a certificate error, even though curl -I from the command line works fine.
  • It happens in every browser you personally use, but a friend on a different machine (or you in a browser you've never visited the site with) loads it normally.
  • The dev/staging subdomain you tested HTTPS on months ago now refuses plain HTTP forever, even after you took the certificate down.

That last symptom — it working fine for new visitors but broken only for you — is the tell. It means the policy is cached in your browser, not misconfigured on your server.

Cause: How HSTS Actually Works

When your server sends a response over HTTPS, it can include this header:

Strict-Transport-Security: max-age=31536000; includeSubDomains; preload

That tells the browser: "for the next max-age seconds, never load this domain over plain HTTP again — rewrite every request to HTTPS yourself, before even asking DNS." The browser stores that instruction locally. It has nothing to do with your server's config once it's cached; the browser enforces it even if your certificate later expires, gets revoked, or you remove HTTPS entirely.

Three details cause almost all the pain:

  • max-age is long by design. A year (31536000 seconds) is typical and recommended. That's a year the browser won't take no for an answer on HTTPS for that domain.
  • includeSubDomains is inherited blindly. If example.com sets it, every subdomain — dev.example.com, mail.example.com, even one that doesn't exist yet — is forced to HTTPS too, whether or not it has a valid cert.
  • preload is close to permanent. Submitting to the HSTS preload list bakes the policy into Chrome, Firefox, Safari, and Edge's source code. Removing your domain from that list can take months, because it ships with browser releases, not a live lookup.

Fix 1: You're Locked Out of Your Own Domain Right Now

If a specific machine can't reach your site over HTTP or is stuck on a stale cert error:

  1. Confirm the cert is actually fine from outside the browser cache:
    curl -Iv https://example.com
    If that returns a clean 200 with a valid, unexpired certificate, the problem is 100% local to the browser, not your server.
  2. Clear the browser's HSTS state for that one domain instead of wiping all history:
    • Chrome: go to chrome://net-internals/#hsts, type the domain under "Delete domain security policies," and click Delete.
    • Firefox: no per-site UI — clearing "Cookies and Site Data" for the site usually clears it too, but a full HSTS cache clear needs about:config work or a profile reset.
  3. If the domain is on the preload list, clearing the local cache won't help other visitors — the policy is shipped with the browser itself. You'll need to fix HTTPS properly (see below) rather than trying to disable HSTS.

Fix 2: Setting Up HSTS Correctly the First Time

Don't reach for max-age=31536000; includeSubDomains; preload on day one. Ramp it up once you're confident every subdomain actually has valid HTTPS.

Apache / cPanel (.htaccess or the domain's vhost include):

<IfModule mod_headers.c>
  Header always set Strict-Transport-Security "max-age=300"
</IfModule>

Nginx:

add_header Strict-Transport-Security "max-age=300" always;

Start with a short max-age like 300 seconds (5 minutes) for a week or two. Test every subdomain, every payment page, every third-party widget that might still load over HTTP. Once nothing breaks, raise it in stages:

Stagemax-ageWhen to use it
Testing300 (5 min)First rollout, easy to undo mistakes
Confidence-building86400 (1 day)After a clean week with no HTTP dependencies found
Production31536000 (1 year)Once every subdomain is confirmed HTTPS-only
Preload submission31536000 + includeSubDomains + preloadOnly after production stage has run cleanly for months

In cPanel, if you're using AutoSSL, note that it does not add this header for you — it just issues and renews the certificate. The header itself is your responsibility, added via .htaccess, the Apache vhost include (via WHM's "Include Editor"), or Nginx config if you're running LiteSpeed/Nginx in front.

Fix 3: Don't Turn On includeSubDomains Casually

This directive is the one that causes support tickets. If example.com sends includeSubDomains and someone spins up api.example.com next month on a server that only serves HTTP, every visitor's browser will refuse to connect to it at all — not a redirect, a hard failure, because the browser won't even attempt HTTP. There's no graceful degradation once it's cached.

Only add includeSubDomains once you're certain every current and near-future subdomain will have valid HTTPS. If you run wildcard subdomains for a SaaS-style setup or add subdomains often, skip this directive, or use a wildcard SSL certificate to make sure new subdomains are covered by default before HSTS ever sees them.

Prevention

  • Never set max-age to a year on the first deploy. Ramp it up over weeks, not hours.
  • Keep a written note of which domains you've submitted to the preload list — removal is slow and not guaranteed.
  • Before adding includeSubDomains, list every subdomain in your DNS zone and confirm each one serves a valid certificate.
  • If you use Cloudflare, be aware it can add its own HSTS header independently of your origin server's — check SSL/TLS → Edge Certificates → HTTP Strict Transport Security (HSTS) so you're not stacking two conflicting policies.
  • Test HSTS changes in a private/incognito window first — it isolates the browser's HSTS cache from your main profile, so you can undo a mistake by just closing the window.

Frequently Asked Questions

Can I just remove the HSTS header to undo it?

Removing the header stops new visitors from receiving the policy, but it does nothing for browsers that already cached it. Their cached max-age keeps counting down on the old rule until it expires, regardless of what your server sends now. The only way to shorten that window is to send a new header with a very low max-age (like 0) over HTTPS — the browser has to visit once more over HTTPS to pick up the shorter expiry.

Does AutoSSL in cPanel set the HSTS header automatically?

No. AutoSSL only issues and renews the TLS certificate itself. The Strict-Transport-Security header is a separate setting you add through .htaccess, an Apache vhost include, or your Nginx/LiteSpeed config — it has nothing to do with certificate issuance.

My site works fine for everyone except me — why?

Because HSTS is cached per-browser, per-domain. If you previously visited the site over HTTPS with the header set, your browser stored that policy locally and now enforces HTTPS on its own, even after server-side changes. New visitors who've never hit your domain won't have that cached entry, so they're unaffected.

What's the risk of submitting to the HSTS preload list?

Preloading bakes your domain into the source code of major browsers, so the policy applies even to a visitor's very first request — before any server response is ever seen. That's great for security, but reversing it means getting removed from a list that ships with browser updates, which can take months to fully propagate across all users. Only preload a domain once HTTPS has been rock-solid across every subdomain for an extended period.

I set includeSubDomains and now a subdomain won't load at all — what do I do?

Get valid HTTPS working on that subdomain as the real fix — a wildcard certificate covering *.example.com is usually the fastest path in cPanel. There's no way to selectively exempt one subdomain from a parent domain's includeSubDomains policy once it's cached in visitors' browsers; the only way out is to make the subdomain actually serve valid HTTPS.