HTTPS Not Forced — Site Serves on Both http and https

Your site responds on both http and https. Google indexes both versions and your security score is downgraded.

You type http://yourdomain.com and the site loads — no redirect to HTTPS. Search Console shows two versions of every URL (http and https) listed separately. Chrome shows “Not Secure” on http requests. Without forced HTTPS, you have a duplicate site as far as Google’s concerned, your security score is reduced, and modern browsers increasingly disfavor unencrypted traffic. The fix is a 301 redirect from http → https at the platform/CDN layer, plus an HSTS header to lock browsers into HTTPS.

Common causes

Ordered by hit rate, highest first.

1. Hosting platform doesn’t auto-force

Some hosts (older Firebase configurations, custom Nginx, certain shared hosting plans) don’t redirect http → https automatically. You have to enable it.

How to spot it:

curl -sI "http://yourdomain.com" | head -3

If returns 200, no redirect. Should be 301 with Location: https://....

2. Cloudflare in “Flexible SSL” mode

“Flexible” terminates SSL at Cloudflare’s edge but talks HTTP to origin. The visitor sees HTTPS but origin still serves HTTP. This is insecure and breaks origin-side HTTPS-only setups.

How to spot it: Cloudflare → SSL/TLS → Overview → if mode is “Flexible”, change to “Full” or “Full (strict)“.

3. HSTS not set

Even with a redirect, browsers still try HTTP first time. HSTS tells browsers “always HTTPS for this domain” so subsequent visits skip the http hop.

How to spot it: curl -sI https://yourdomain.com | grep -i strict-transport-security. Empty = no HSTS.

4. App-level redirect (not platform-level)

You added a redirect in your app code (Express, Astro middleware) but it only fires after the app receives the request. Some HTTP requests never get there (CDN intercepts, static asset paths).

How to spot it: Some URLs redirect, others don’t. The ones that don’t are usually static assets served before app code runs.

5. Custom CDN / proxy serves http directly

A CDN in front of your origin is configured to serve both http and https. Without explicit rule, http stays as http.

How to spot it: Bypass CDN (curl origin directly) — see if origin redirects. If origin redirects but CDN doesn’t, CDN config issue.

6. Mixed-content issues block HSTS

Pages with <img src="http://..."> or other mixed content can prevent HSTS from being trusted by the browser. Not strictly “HTTPS not forced,” but related symptoms.

How to spot it: DevTools console → look for “Mixed Content” warnings.

Shortest path to fix

Step 1: Enable platform force-HTTPS

Vercel — Enabled by default. Confirm in Domain settings.

Netlify — Site settings → Domain management → HTTPS → “Force HTTPS.” Check the box.

Firebasefirebase.json:

{
  "hosting": {
    "redirects": [
      {
        "source": "**",
        "destination": "https://yourdomain.com/:1",
        "type": 301
      }
    ]
  }
}

Or use Firebase’s built-in (newer projects auto-force).

Cloudflare — SSL/TLS → Edge Certificates → “Always Use HTTPS” → On.

Step 2: Add HSTS header

Vercelvercel.json:

{
  "headers": [{
    "source": "/(.*)",
    "headers": [
      { "key": "Strict-Transport-Security", "value": "max-age=63072000; includeSubDomains; preload" }
    ]
  }]
}

Netlify_headers:

/*
  Strict-Transport-Security: max-age=63072000; includeSubDomains; preload

Start with shorter max-age (300 = 5 min) to test, then raise to 63072000 (2 years) once confident.

Step 3: For Cloudflare, use Full (strict)

SSL/TLS → Overview → choose “Full (strict).” Cloudflare encrypts to origin and verifies origin’s cert.

If origin has cert issues, fix origin first (use platform cert or Cloudflare Origin Certificate).

Step 4: Verify with curl

curl -sI "http://yourdomain.com" | head -5

Should return:

HTTP/1.1 301 Moved Permanently
Location: https://yourdomain.com/

And for HTTPS:

curl -sI "https://yourdomain.com" | grep -i strict-transport-security

Should show the HSTS header.

Step 5: Submit https URLs in Search Console

Search Console → URL Inspection → request indexing for the https version of important URLs. Google merges http into https as the canonical.

Step 6: Consider HSTS preload

After running HSTS smoothly for months, submit your domain to HSTS preload list. Once preloaded, all browsers force HTTPS for your domain from first visit, even before they’ve contacted your server. Caveat: removal from preload list takes weeks. Don’t preload until you’re 100% certain HTTPS will always work.

When this is not on you

Some older hosting configurations require manual force-HTTPS toggling. Recent platforms (Vercel, Netlify, Cloudflare Pages) force by default.

Easy to misdiagnose as

Setting only <link rel="canonical" href="https://..."> doesn’t prevent Google from indexing the http version. The 301 redirect is what actually de-indexes http.

Prevention

  • Force HTTPS at the platform level from day one. Don’t ship a site without it.
  • Add HSTS with a long max-age (2 years) once HTTPS is stable for 30+ days.
  • Use “Full (strict)” SSL mode on Cloudflare, never “Flexible.”
  • Verify with curl -sI http://yourdomain.com that it returns 301 after any infrastructure change.
  • Audit for mixed-content warnings (<img src="http://...">) and fix them.

FAQ

  • Does HSTS prevent rollback? Yes — that’s its purpose. Browsers won’t try http for max-age seconds. Test HTTPS works reliably before raising max-age. To exit HSTS, set max-age=0; preload list removal takes weeks.
  • Do I need a paid SSL cert? No — Let’s Encrypt or platform-provided certs work fine for the vast majority of sites.

Tags: #Domain #DNS #SSL #Troubleshooting #Force HTTPS