Installing a certificate does not move anyone to HTTPS. It makes HTTPS available. Every link, bookmark and typed address that says http:// still reaches the unencrypted version of the site until something tells it to go elsewhere, and that something is a redirect.
The rule is short on every platform. What goes wrong is where it is placed, how many hops it produces, and whether the server can tell which protocol the visitor actually used.
What a correct redirect looks like
Three properties, all of which can be checked from outside:
- One hop.
http://example.com/pagegoes directly to the final HTTPS address, not through an intermediate stop. - A 301 or 308. The move to HTTPS is permanent, so the code should say so. A 302 leaves search engines treating the
http://address as the real one. - The path survives.
/pricingredirects to/pricingon HTTPS, not to the homepage.
Run the http:// form of your address through the Redirect Checker. A correct setup shows two rows: a 301 served over plain HTTP, then a 200 over HTTPS. Anything longer, or any 302, is one of the problems below.
Apache and .htaccess
RewriteEngine On
RewriteCond %{HTTPS} off
RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
Put it at the top of the file, above anything a CMS has written. Rules run in order, and an HTTPS rule placed below the application's own rewrite block frequently never fires, because the earlier block has already handled the request.
The R=301 matters. R on its own produces a 302.
Nginx
Give plain HTTP its own server block that does nothing except redirect:
server {
listen 80;
server_name example.com www.example.com;
return 301 https://example.com$request_uri;
}
return is preferable to rewrite here: it is evaluated without a regular expression and it cannot be accidentally combined with other rules. Naming the final hostname explicitly, rather than using $host, is what makes this a single hop — more on that below.
IIS
Using the URL Rewrite module, in web.config:
<rule name="HTTPS" stopProcessing="true">
<match url="(.*)" />
<conditions>
<add input="{HTTPS}" pattern="off" ignoreCase="true" />
</conditions>
<action type="Redirect" url="https://{HTTP_HOST}/{R:1}" redirectType="Permanent" />
</rule>
The module is a separate install. If the rule is present and nothing happens, check that URL Rewrite is actually installed before debugging the rule itself.
WordPress
WordPress stores its own address in two settings, WordPress Address and Site Address, under Settings → General. Change both to https://. WordPress then redirects requests for its own pages to the stored address.
That alone is not complete. It covers pages WordPress generates, and not necessarily static files served directly by the web server. Keep the server-level rule as well, and make sure the two agree on the hostname — a server rule forcing www against a Site Address without it is the most common cause of a redirect loop.
A CDN or managed host
Most CDNs and hosting control panels have a single switch, usually labelled something like "Always use HTTPS" or "Force HTTPS". On Cloudflare it is under SSL/TLS → Edge Certificates. On cPanel it is a toggle in the Domains list.
Use the switch or an origin rule, and be deliberate about which. Having both is where the proxy loop comes from.
When the redirect loops: the proxy case
If the site sits behind a CDN or load balancer, the proxy usually terminates TLS and talks to your server over plain HTTP. Your server's HTTPS off condition is then true for every request, including the ones visitors made securely. It redirects to HTTPS, the proxy forwards over HTTP again, and the browser reports ERR_TOO_MANY_REDIRECTS.
The origin has to read the header the proxy sets instead of inspecting its own connection:
RewriteCond %{HTTP:X-Forwarded-Proto} !https
RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
On Cloudflare the same loop appears whenever SSL mode is Flexible and the origin redirects to HTTPS. Set it to Full (strict), which also means the connection between Cloudflare and your server is actually encrypted rather than only appearing to be.
Combining it with the www rule
This is the mistake that passes every casual test. Two tidy rules, each correct alone:
http://example.com → https://example.com → https://www.example.com
That is two hops where one would do. Every visitor arriving on the bare http:// address pays an extra round trip, and the first redirect points at a URL that is itself not final.
Write the rule so it names the destination in full:
RewriteCond %{HTTPS} off [OR]
RewriteCond %{HTTP_HOST} !^www\. [NC]
RewriteRule ^ https://www.example.com%{REQUEST_URI} [L,R=301]
Then test all four entry points — http:// and https://, with and without www. Three of them should reach the fourth in exactly one hop. Redirect chains covers why the extra hop is worth removing.
Why it appears not to be working
You are looking at a cached redirect. Browsers cache 301s, often indefinitely. A rule you changed ten minutes ago may never be requested again by the browser you are testing in. Use a private window, or a tool that makes a fresh request every time.
The browser upgraded the request itself. Modern browsers try HTTPS first for typed addresses, and a site sending an HSTS header is upgraded internally before any request leaves the machine. Either way you see HTTPS in the address bar without your server's redirect having run at all. This is why "it works when I type it" proves nothing about the rule.
The rule is in a file the server does not read. .htaccess is ignored when AllowOverride None is set, and is never read by Nginx at all.
Only some paths redirect. A rule scoped to one directory, or placed after a block that ends processing for certain URLs, redirects the homepage and leaves deeper pages on HTTP. Test a real inner page, not just the root.
The check that cuts through all four is the same: request the http:// address from somewhere that has no cache and no HSTS memory. From a terminal:
curl -sI http://example.com/some-page
The first line should be 301, and the Location header should be the final HTTPS address.
After the redirect works
Three things the redirect does not fix on its own.
Mixed content. Pages now load over HTTPS, but images, scripts or stylesheets hard-coded with http:// are blocked or flagged, and the browser can still say Not Secure with a perfectly valid certificate. Why your site says Not Secure covers finding them.
Internal links. A link to http://example.com/about still works, through the redirect, on every click. Update internal links, the sitemap and canonical tags to the HTTPS address so the redirect is a safety net rather than the normal route.
HSTS, once you are sure. The Strict-Transport-Security header tells browsers to skip the HTTP request entirely on future visits. It removes the redirect round trip and closes the window in which a first request travels unencrypted. Start with a short max-age and raise it later: a browser that has received the header will refuse plain HTTP for that long, and there is no way to recall it.
Finally, trace the http:// address once more with the Redirect Checker and confirm one hop, a 301, and a 200 on HTTPS. Also confirm the certificate behind it is valid with the SSL Checker — redirecting visitors to an HTTPS address with a broken certificate replaces a quiet problem with a full-page warning.