Problem
You have a valid certificate, https:// works, and you assume you’re done. Then someone types your bare domain, or clicks an old http:// link from an email three years ago, and lands on the plaintext version of your site — no lock icon, mixed with the secure one in search results, and quietly readable by anyone on the same coffee-shop Wi-Fi. Having HTTPS available is not the same as forcing it. Until every HTTP request gets bounced to HTTPS, you have two versions of your site, and the insecure one is still open.
Forcing the redirect is a five-minute job. What makes it fiddly is that three separate things all have to agree — the redirect status code, the HSTS policy, and whatever proxy or CDN sits in front of your origin — and when they disagree you get either a site that’s still reachable over plaintext or a site that won’t load at all.
What “forcing HTTPS” actually means
There are two layers, and people conflate them.
The redirect is server-side: a request comes in on port 80 (http://), and instead of serving the page, your server answers with a 3xx status and a Location: header pointing at the https:// version. The browser follows it. This works for every client, including old ones and bots, but it only fires after the plaintext request has already gone out.
HSTS is client-side memory: a header you send over HTTPS that tells the browser “for the next N seconds, never even try http:// for this domain — rewrite it to https:// before touching the network.” That eliminates the plaintext request entirely, but only for browsers that have already visited you once over HTTPS.
You want both. The redirect catches everyone; HSTS protects the repeat visitors from that dangerous first cleartext hop. Neither one alone is complete.
Pick the redirect code: 301, not 302 (and when to use 308)
Use a 301 Moved Permanently. The move from HTTP to HTTPS is permanent — you’re never going back — so a 301 is honest about that, search engines fold the HTTP URL into the HTTPS one as canonical, and browsers cache the redirect so they stop asking. A 302 (temporary) tells everyone the plaintext URL might come back, which is wrong and wastes a round trip on every visit.
The one real trap is POST requests. RFC 9110 still permits a client to rewrite a 301 or 302 into a GET, which historically many did. If your hostname also receives form posts or API calls, a client that hits the HTTP endpoint can have its method and body silently dropped on the redirect. If that’s a risk for you, use a 308 Permanent Redirect (§15.4.9), which is the permanent redirect that must preserve the method and body. Rule of thumb: 301 for a content site, 308 if the same host also takes POSTs.
The trap that breaks the whole thing: the redirect loop
This is the failure that generates the most panicked searches, and it’s almost never a bug in your redirect rule. You add “redirect all HTTP to HTTPS,” and now the browser shows ERR_TOO_MANY_REDIRECTS and refuses to load anything.
It happens whenever something terminates TLS in front of your origin and then talks to the origin over plain HTTP. The classic case is Cloudflare’s “Flexible” SSL mode:
- The browser connects to Cloudflare over HTTPS. Good so far.
- Cloudflare, in Flexible mode, forwards the request to your origin over plain HTTP.
- Your origin sees an HTTP request and dutifully answers
301 → https://. - Cloudflare sends that redirect back to the browser, which retries over HTTPS…
- …which Cloudflare again forwards to the origin as HTTP. Go to step 3. Forever.
The fix is not to remove your redirect. It’s to make the front-end speak HTTPS to the origin too: set Cloudflare’s SSL/TLS mode to Full (Strict). The same shape shows up with any reverse proxy or load balancer that terminates TLS — nginx, an AWS ALB, Kubernetes ingress. There, the answer is to stop redirecting on the raw connection scheme and instead read the X-Forwarded-Proto header the proxy adds: if it says https, the original request was already secure, so don’t redirect. Redirect only when X-Forwarded-Proto is http.
If a request went through no proxy the header won’t be present, so treat a missing header as HTTP and redirect. The mistake is trusting the local socket, which behind a TLS-terminating proxy always looks like plaintext.
After it works: don’t stop at the redirect
Add HSTS. Once HTTPS is forced, send Strict-Transport-Security: max-age=31536000; includeSubDomains on your HTTPS responses. Now returning visitors skip the plaintext request altogether — the browser turns http:// into https:// in memory (you’ll see a 307 Internal Redirect with Non-Authoritative-Reason: HSTS in DevTools, not a network round trip). Start with a shorter max-age while you confirm nothing breaks, then raise it. Only add preload and submit to the browser preload list once you’re certain every subdomain can do HTTPS — preload is hard to undo quickly.
Watch for mixed content. The moment the page is served over HTTPS, any http:// image, script, or stylesheet it pulls in becomes mixed content — browsers block active mixed content (scripts) outright and warn on the rest. A page can redirect perfectly and still show a broken lock because one hardcoded http:// asset slipped through.
How to check it
Verify from the outside, the way a browser or a bot sees you — not from a config file that should be right:
- Request the plaintext URL and read the status. Hit
http://yourdomain.comand confirm you get a single301(or308) whoseLocationis the exacthttps://equivalent — same path, no surprise detour to the homepage. - Count the hops. You want one redirect,
http://straight to the finalhttps://URL. If you seehttp → https → https-with-www → …, you’ve stacked redirect rules, and every extra hop is latency and a chance to leak. - Confirm the HSTS header is actually there. Look at the headers on the
https://response, not the redirect — the redirect response often can’t carry a header the browser will trust. - Test
wwwand the apex, and a deep path. People redirect the homepage and forget thathttp://yourdomain.com/old/pageneeds to land onhttps://yourdomain.com/old/page, not get dumped at the root.
Check it with DechoNet
- HTTP Check follows the redirect chain from
http://and shows you the exact status code of every hop, the final URL, and the response headers — so you can confirm it’s a single clean 301/308 and see whether theStrict-Transport-Securityheader is present on the HTTPS response. - SSL Check confirms the certificate you’re forcing everyone onto is actually valid and complete — a forced redirect to a broken or expired certificate just moves the error, it doesn’t fix it.
Resolution Checklist
- Redirect all
http://traffic to thehttps://equivalent with a 301 (or 308 if the host also receives POSTs). Preserve the full path, don’t dump everyone at the homepage. - If you’re behind a CDN or TLS-terminating proxy, make sure it speaks HTTPS to your origin (Cloudflare: Full (Strict)) or redirect based on
X-Forwarded-Proto, not the raw socket — this is what preventsERR_TOO_MANY_REDIRECTS. - Send
Strict-Transport-Securityon HTTPS responses; start with a modestmax-age, addincludeSubDomainsonce every subdomain does HTTPS, and onlypreloadwhen you’re sure. - Fix any mixed content — hunt down hardcoded
http://assets so the lock icon is clean. - Verify from outside: one clean redirect hop, correct final URL, and HSTS present on the HTTPS response. Test
www, the apex, and a deep path.
When to Escalate
- If you get
ERR_TOO_MANY_REDIRECTSand you’re not behind a CDN, look for two layers both trying to force HTTPS — e.g. an application-level rule and a web-server rule that disagree, or an HTTP→HTTPS rule fighting an apex↔www rule. Turn off one layer’s redirect and let a single owner handle it. - If HSTS is set but browsers still hit HTTP, remember it only takes effect after one successful HTTPS visit — and once preloaded, it’s cached by the browser vendor and slow to remove. Don’t preload a domain whose subdomains can’t all serve HTTPS yet.
- If the redirect is correct but the certificate itself fails (name mismatch, expired, missing intermediate), that’s an SSL problem, not a redirect one — forcing everyone onto a broken certificate is worse than the plaintext you started with.
Check your own domain now
Free, no sign-up. Runs the exact check this guide describes and shows what to fix.