HTTPS Not Forced — Site Loads on Both http and https

Your site answers on both http and https, so Google indexes two copies and Chrome flags http as Not Secure. Add a 301 redirect plus an HSTS header to fix it.

You type http://yourdomain.com and the page loads with no redirect to HTTPS. Search Console lists the http and https version of every URL separately. Chrome shows “Not Secure” on the http requests. To Google this is a duplicate site, your crawl budget is split, and the unencrypted copy can outrank or sit alongside the secure one.

Fastest fix: turn on your host’s built-in force-HTTPS toggle (one switch on Vercel, Netlify, Cloudflare, and Firebase). That issues a 301 from http to https before the request reaches your app. Then add a Strict-Transport-Security (HSTS) header so browsers skip the http hop entirely on return visits. Both take effect in minutes. The platform paths below are current as of June 2026.

Confirm the symptom in 5 seconds

curl -sI "http://yourdomain.com" | head -3
  • HTTP/1.1 200 (or HTTP/2 200) means http is being served directly — not forced, keep reading.
  • HTTP/1.1 301 Moved Permanently with Location: https://... means the redirect is already working; your remaining issue is probably HSTS or a stale Search Console index.

Which bucket are you in

Symptom from curl -sI http://yourdomain.comLikely causeJump to
200 on every pathHost force-HTTPS toggle is offStep 1
301 but the page still looks insecure / origin is plain httpCloudflare in Flexible modeStep 3
301 works, but no strict-transport-security header on httpsHSTS not setStep 2
Some paths 301, some 200 (often static assets)Redirect lives in app code, not the platformStep 1
301 works but Search Console still shows both versionsIndex not yet mergedStep 5

Common causes

Ordered by hit rate, highest first.

1. Host’s force-HTTPS toggle is off

Most modern hosts can redirect http to https for you, but a few configurations (older custom Nginx, some shared hosting, a self-managed origin behind a CDN) leave it off until you flip the switch.

How to spot it: curl -sI "http://yourdomain.com" | head -3 returns 200 instead of a 301.

2. Cloudflare in “Flexible” SSL mode

“Flexible” terminates TLS at Cloudflare’s edge but talks plain HTTP to your origin. The visitor’s browser shows a padlock, yet the origin leg is unencrypted, which also defeats any HTTPS-only setup on the origin. Cloudflare still supports Flexible but now strongly recommends Full or Full (strict), and its Automatic SSL/TLS feature (rolling out since late 2024) may upgrade the mode for you.

How to spot it: Cloudflare dashboard → SSL/TLS → Overview. If the encryption mode reads “Flexible,” that’s your problem.

3. HSTS not set

Even with a working 301, a browser’s first request of a session still goes out over http before it follows the redirect. HSTS tells the browser “use HTTPS for this domain for the next N seconds,” so every later visit skips the http hop entirely.

How to spot it:

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

Empty output means no HSTS header.

4. Redirect lives in app code, not the platform

A redirect added in app code (Express middleware, Astro middleware, a framework config) only fires after the request reaches your app. Requests intercepted earlier — by the CDN, or for static assets served before app code runs — never get redirected.

How to spot it: some URLs redirect and others (usually static files like /favicon.ico or /robots.txt) return 200 over http. Move the redirect to the platform/edge layer.

5. A CDN or proxy serves http directly

A CDN in front of your origin can be configured to answer on both http and https. Without an explicit “redirect to https” rule, http stays http.

How to spot it: curl the origin directly (bypassing the CDN). If the origin returns a 301 but the public URL does not, the CDN config is the gap.

6. Mixed content undermines the secure page

Pages that pull a sub-resource over http — <img src="http://...">, a script, or a stylesheet — trigger “Mixed Content” warnings. This does not stop HSTS from being sent, but it erodes the secure padlock and is worth fixing alongside.

How to spot it: DevTools → Console → look for Mixed Content warnings.

Shortest path to fix

Step 1: Turn on platform force-HTTPS

Vercel — Force HTTPS is automatic for custom domains; there is no toggle to enable. Confirm the domain shows a valid certificate under Project → Settings → Domains.

NetlifyDomain management → HTTPS, then enable Force HTTPS. (The older “Site settings →” prefix is gone; the section is now top-level Domain management as of June 2026.) The toggle only appears once Netlify has provisioned the Let’s Encrypt certificate.

CloudflareSSL/TLS → Edge Certificates → Always Use HTTPS → On. This issues a 301 for every http request at Cloudflare’s edge, across all paths and subdomains, before anything reaches your origin.

Firebase Hosting — Force-HTTPS is automatic: Firebase serves a 301 from http to https on every Hosting domain by default, so you do not need a redirect rule in firebase.json for this. (A redirects block is only for redirecting between URLs, not for forcing the protocol.) If http is still not redirecting, the domain is likely being served by something other than Firebase Hosting — recheck your DNS records point to Firebase.

Step 2: Add the HSTS header

Vercel — Vercel already sends Strict-Transport-Security: max-age=63072000 on custom domains automatically, so you usually do not need to set it. To customize the value (for example to add includeSubDomains; preload), set it in vercel.json:

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

Netlify — add a _headers file in your publish directory:

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

CloudflareSSL/TLS → Edge Certificates → HTTP Strict Transport Security (HSTS) → Enable HSTS, then set Max-Age and includeSubDomains in the dialog.

Start with a short max-age (300 = 5 minutes) to confirm nothing breaks, then raise it to 63072000 (2 years) once you are confident. 63072000 is the common production value; the minimum required for preload is 31536000 (1 year).

Step 3: On Cloudflare, use Full (strict)

SSL/TLS → Overview → Full (strict). Cloudflare then encrypts the connection to your origin and validates the origin’s certificate, closing the gap that “Flexible” leaves open.

If the origin certificate is the blocker, fix the origin first — install a platform certificate or a free Cloudflare Origin Certificate. Don’t disable validation to “make it work”; that reintroduces the same exposure as Flexible.

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 on the https side:

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

Should print the HSTS header. Test the bare apex and www, plus at least one static asset path (/favicon.ico), since those are where app-level redirects tend to leak.

Step 5: Re-point Search Console to https

Search Console → URL Inspection → request indexing for the https version of your top URLs. Once the 301 is live, Google consolidates the http URLs onto the https canonical automatically over the next few crawls — you don’t have to remove the http ones manually.

Step 6: (Optional) HSTS preload

After HSTS has run cleanly for a few weeks, submit your domain at hstspreload.org. Once preloaded, browsers force HTTPS for your domain from the very first visit, before they ever contact your server. The submitted header must include max-age of at least 31536000 (1 year), plus includeSubDomains and preload.

Caveat: removal from the preload list takes weeks to months to propagate through browser releases. Don’t preload until you are certain every current and future subdomain will always serve valid HTTPS.

How to confirm it’s fixed

  • curl -sI http://yourdomain.com returns 301 with a Location: https://... header — on the apex, on www, and on a static asset path.
  • curl -sI https://yourdomain.com | grep -i strict-transport-security shows the header.
  • A fresh load in Chrome of http://yourdomain.com lands on https with a padlock and no “Not Secure” badge.
  • DevTools Console is free of Mixed Content warnings.

Easy to misdiagnose as

Setting only <link rel="canonical" href="https://..."> does not stop Google from indexing the http version — canonical is a hint, not a redirect. The 301 is what actually moves the http URL out of the index.

Prevention

  • Force HTTPS at the platform level on day one. Don’t ship a site without it.
  • Add HSTS with a long max-age (2 years) once HTTPS has been stable for 30+ days.
  • Use Full (strict) SSL mode on Cloudflare, never Flexible.
  • After any infrastructure change, re-run curl -sI http://yourdomain.com and confirm it still returns 301.
  • Audit for mixed content (<img src="http://...">) and switch sub-resources to https or protocol-relative URLs.

FAQ

  • Does HSTS prevent rollback to http? Yes, that’s the point — browsers refuse http for max-age seconds. So test that HTTPS works reliably before raising max-age. To unwind it, serve Strict-Transport-Security: max-age=0; if you also preloaded, removal from the preload list takes weeks.
  • Do I need a paid SSL certificate? No. Let’s Encrypt or your platform’s bundled certificate covers the vast majority of sites at no cost.
  • 301 or 302 for the http→https redirect? Use 301 (permanent). A 302 tells Google the move is temporary, so it may keep the http URL indexed. Every platform toggle above uses 301.
  • My host shows 200 over http but the padlock looks fine in the browser — am I done? No. The browser padlock only reflects the https request you happened to make. If curl -sI http://... still returns 200, the http copy is live and indexable. Fix the redirect.
  • Will forcing HTTPS hurt my SEO or rankings? No — Google has used HTTPS as a (light) ranking signal since 2014 and consolidating to a single https canonical removes duplicate-content dilution. Expect a short re-crawl period, not a penalty.

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