Skip to content
SiteCheckTools

Articles/Redirect Checker

How to Redirect HTTP to HTTPS, and Check It Works

A certificate makes HTTPS available. It moves nobody there. The rule that does is three lines long, and most of what goes wrong is where it was put.

·8 min read

The tool this explains

Redirect Checker

Trace every hop in a redirect chain with its status code, and flag long chains and redirect loops.

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/page goes 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. /pricing redirects to /pricing on 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.

Check this on your own domain with the Redirect Checker. Free, no signup, and every result shows the raw data behind it.