Back to tutorials
Tutorial

HTTPS Redirect Troubleshooting Tutorial (2026): Fix Redirect Loops, Mixed Content, and Wrong Scheme on a VPS or cPanel

HTTPS redirect troubleshooting tutorial for loops, mixed content, and wrong scheme on Nginx/Apache/cPanel—fix headers, proxies, and WordPress.

By Anurag Singh
Updated on Sep 22, 2026
Category: Tutorial
Share article
HTTPS Redirect Troubleshooting Tutorial (2026): Fix Redirect Loops, Mixed Content, and Wrong Scheme on a VPS or cPanel

A broken HTTPS redirect hurts quickly. Visitors hit “too many redirects,” checkout flows fail, and crawlers keep retrying until your logs fill up. This HTTPS redirect troubleshooting tutorial shows how to untangle redirect loops, mixed-content warnings, and “wrong scheme” behavior on Nginx, Apache, and cPanel. These issues often appear after a migration, a CDN change, or adding a reverse proxy.

The steps assume you manage a Linux VPS or dedicated server (Ubuntu/Debian/AlmaLinux/Rocky). If you don’t want to chase proxy headers, AutoSSL quirks, and edge-rule conflicts, managed VPS hosting from HostMyCode can handle the platform side while you focus on the site.

What you’ll diagnose (and the fastest way to identify the culprit)

Most HTTPS failures fall into a few predictable categories:

  • Redirect loop: HTTP→HTTPS is enforced in more than one place (web server + WordPress + CDN + control panel).
  • Wrong scheme behind a proxy: your app believes requests are HTTP, even though visitors arrive over HTTPS.
  • Mixed content: the page is HTTPS, but scripts/images/fonts still load over HTTP.
  • Host mismatch: www vs non-www rules fight each other, or your canonical URL disagrees with what the server forces.
  • Cache confusion: a browser or CDN stored an old 301 and keeps applying it.

Start with two commands. They show where the redirect happens and whether it repeats.

# 1) Follow redirects and show each hop
curl -IL http://example.com

# 2) Force HTTPS and show the final response
curl -IL https://example.com

Scan the output for:

  • Location: headers bouncing between http and https
  • Host flips (example.com ⇄ www.example.com)
  • Long chains of 301/302 responses that repeat the same pattern

If DNS changed recently, confirm you’re hitting the server you think you are.

Keep this nearby: DNS cutover tutorial.

Step 1: Reproduce the loop with headers (browser lies; curl doesn’t)

Browsers cache redirects aggressively. That makes troubleshooting noisy and inconsistent.

Use curl instead. If you’re validating a new IP before a DNS flip, add an explicit host mapping.

# Replace NEW_IP with your VPS IP; keep Host header as the real domain
curl -IL --resolve example.com:80:NEW_IP http://example.com
curl -IL --resolve example.com:443:NEW_IP https://example.com

If the loop shows up only on HTTPS, the usual causes are:

  • Application-level redirects (WordPress, Laravel, etc.) colliding with web server rules
  • Proxy/CDN headers not being passed or trusted (especially X-Forwarded-Proto)
  • Cloudflare “Flexible” SSL (or an equivalent edge mode) forcing HTTP to the origin

If Cloudflare sits in front, check the SSL mode and DNS records first.

Walkthrough: Cloudflare DNS setup guide.

Step 2: Find duplicate redirects (server + app + control panel)

A stable setup enforces “HTTPS is mandatory” in one place.

Pick the layer on purpose:

  • Option A: enforce HTTPS at the web server (recommended for VPS/dedicated).
  • Option B: enforce HTTPS inside the application (sometimes required for complex multi-tenant apps).
  • Option C: enforce HTTPS at the CDN/edge only (fine if the origin also supports HTTPS and headers are correct).

Loops happen when you stack A + B.

They also happen with A + C when the proxy header chain is wrong.

Quick checklist: where to look

  • Nginx: /etc/nginx/sites-enabled/ and /etc/nginx/conf.d/
  • Apache: /etc/apache2/sites-enabled/ (Debian/Ubuntu) or /etc/httpd/conf.d/ (AlmaLinux/Rocky)
  • WordPress: Site URL / Home URL, plus plugins that “force SSL”
  • cPanel/WHM: AutoSSL + redirects set inside an app, plus any .htaccess rules
  • CDN/WAF: “Always Use HTTPS” toggles and edge redirect rules

Step 3: Fix “too many redirects” on Nginx (common patterns and safe config)

On Nginx, keep the structure simple.

Use one HTTP server block that redirects, and one HTTPS server block that serves.

Avoid clever rewrite logic until the basics work.

# /etc/nginx/sites-available/example.com
server {
  listen 80;
  listen [::]:80;
  server_name example.com www.example.com;

  return 301 https://example.com$request_uri;
}

server {
  listen 443 ssl http2;
  listen [::]:443 ssl http2;
  server_name example.com;

  # SSL cert paths (Let's Encrypt example)
  ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
  ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

  root /var/www/example.com/public;
  index index.php index.html;

  # If you also need www → apex redirect, do it here (one direction only)
}

server {
  listen 443 ssl http2;
  listen [::]:443 ssl http2;
  server_name www.example.com;

  ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
  ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

  return 301 https://example.com$request_uri;
}

Test first, then reload:

sudo nginx -t
sudo systemctl reload nginx

On multi-domain servers, shared includes are a frequent trap.

One redirect in a global snippet can break every vhost.

For a clean pattern, see: Nginx server blocks tutorial.

Step 4: Fix loops on Apache (.htaccess vs vhost rules)

Apache loops usually come from competing rewrite logic.

A common mistake is stacking a VirtualHost redirect with a WordPress plugin that also forces SSL. That creates a back-and-forth.

A minimal VirtualHost approach looks like this:

# Debian/Ubuntu: /etc/apache2/sites-available/example.com.conf
<VirtualHost *:80>
  ServerName example.com
  ServerAlias www.example.com

  Redirect permanent / https://example.com/
</VirtualHost>

<IfModule mod_ssl.c>
<VirtualHost *:443>
  ServerName example.com
  ServerAlias www.example.com

  SSLEngine on
  SSLCertificateFile /etc/letsencrypt/live/example.com/fullchain.pem
  SSLCertificateKeyFile /etc/letsencrypt/live/example.com/privkey.pem

  DocumentRoot /var/www/example.com/public

  # If you need www → non-www, do it once, clearly:
  RewriteEngine On
  RewriteCond %{HTTP_HOST} ^www\.example\.com$ [NC]
  RewriteRule ^ https://example.com%{REQUEST_URI} [R=301,L]
</VirtualHost>
</IfModule>

Enable modules and reload (Debian/Ubuntu):

sudo a2enmod ssl rewrite headers
sudo apachectl -t
sudo systemctl reload apache2

If your redirect lives in .htaccess, audit it line by line.

Look for duplicate “force https” blocks.

Keep one set of rules. Delete the rest.

Step 5: Fix HTTPS detection behind a reverse proxy or CDN (X-Forwarded-Proto)

This is the classic “wrong scheme” loop.

The visitor arrives on https://, but the origin app thinks it’s HTTP.

The app redirects to HTTPS. The proxy retries. You end up spinning.

First, confirm what the origin actually receives.

On Nginx + PHP-FPM, log headers temporarily (ideally restricted to your IP). You can also check app logs if they already record request details.

In WordPress, a temporary PHP file that prints $_SERVER (placed in a protected path) can be enough to confirm what’s happening.

Nginx: pass and trust the right proto

If Nginx proxies to Apache or PHP-FPM, make sure these headers are set:

proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host;

Behind a CDN (like Cloudflare), the scheme may arrive via CF-Visitor or standard X-Forwarded-Proto.

That depends on configuration.

What matters is consistency.

You need one trusted source of truth from edge to origin.

Apache behind a proxy: RemoteIP and HTTPS env

If Apache sits behind a TLS-terminating proxy, you may need to tell Apache that HTTPS is effectively “on” when X-Forwarded-Proto is https. Example:

RewriteEngine On
RewriteCond %{HTTP:X-Forwarded-Proto} =https
RewriteRule .* - [E=HTTPS:on]

Don’t apply this blindly.

Only trust these headers when you control the proxy or you’ve restricted direct access to the origin.

Otherwise, clients can spoof them.

Step 6: WordPress-specific fixes (site URLs, wp-config, and mixed content)

WordPress adds two extra hiding spots.

The first is the database values for Site URL/Home URL. The second is plugins that enforce SSL or canonical URLs.

Check Site URL and Home URL via WP-CLI

cd /var/www/example.com/public
wp option get siteurl
wp option get home

If they’re wrong, set them explicitly:

wp option update siteurl 'https://example.com'
wp option update home 'https://example.com'

If WP-CLI is failing because of redirects, file ownership, or permissions, use this workflow to stabilize it: WP-CLI troubleshooting tutorial.

Force HTTPS (only if you must) in wp-config.php

If TLS terminates upstream and WordPress keeps treating requests as HTTP, this can be a practical stopgap:

// /var/www/example.com/public/wp-config.php
if (!empty($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https') {
  $_SERVER['HTTPS'] = 'on';
}

Use it only after you’ve verified your proxy headers.

If you can fix the proxy behavior cleanly, do that instead.

Fix mixed content (the practical approach)

Mixed content isn’t a redirect loop. But it often appears right after you “fix HTTPS.”

The browser console will report blocked resources, and the padlock won’t stick.

  • Update hard-coded URLs in the database from http://example.com to https://example.com.
  • Fix theme/plugin assets that reference HTTP explicitly.
  • Ensure your CDN or asset domain serves HTTPS as well.

WP-CLI search-replace is usually the fastest safe move:

wp search-replace 'http://example.com' 'https://example.com' --skip-columns=guid

Don’t replace GUIDs unless you’re solving a specific problem and understand the impact.

It can break feeds and confuse plugins.

Step 7: cPanel/WHM quirks (AutoSSL, redirects, and proxy subdomains)

On cPanel servers, redirects can be enforced at several layers.

Common ones are Apache vhosts, user .htaccess, application settings, and sometimes edge services.

If the loop affects only one account, start with that user’s rewrite rules and app settings.

Two practical guidelines:

  • Pick one redirect location: either inside the app or in .htaccess. Don’t stack “Force HTTPS Redirect” plugins on top of server rules.
  • Audit proxy subdomains (webmail, cpanel, whm). A bad redirect there can look like “the whole domain is broken” even if the site itself is fine.

If your cPanel server is internet-facing, harden it while you’re in here.

HTTPS issues often show up alongside brute-force noise and messy service configs.

Pair this with: cPanel hardening tutorial.

Step 8: Clear the right caches (301s stick around)

After you fix the config, you may still see the old behavior.

Something may have cached a redirect.

  • Browser: test in a private window, or clear “site data” for the domain.
  • CDN: purge cache (and any page rules) for the affected hostnames.
  • Server: purge full-page caches after scheme/URL changes.

If you use Nginx FastCGI cache for WordPress, keep a reliable purge process handy. Here’s a configuration that avoids breaking login and WooCommerce: WordPress full-page caching tutorial.

Step 9: Verify your fix with a tight acceptance checklist

Don’t stop when the homepage loads.

Confirm the whole site behaves consistently:

  • HTTP → HTTPS works for both apex and www (whichever you use)
  • One redirect hop for canonicalization (no chains)
  • /wp-admin and login screens don’t loop
  • Checkout/cart pages load without mixed content
  • HSTS stays off until HTTPS is stable everywhere (including required subdomains)
# Expect: one 301 to canonical HTTPS, then 200
curl -IL http://example.com/

# Expect: 200 (or 301 only for www → apex), no http bounce
curl -IL https://example.com/

If you’re planning to tighten TLS, do it after redirects are boring and predictable.

Guide: TLS hardening tutorial.

Common “gotchas” that keep coming back

  • Cloudflare Flexible SSL: the browser uses HTTPS, but Cloudflare talks to your origin over HTTP. The origin redirects to HTTPS, and you loop. Use Full (strict) with a valid origin cert.
  • Two canonical rules: one rule forces www, another forces non-www. Pick one direction.
  • Hard-coded HTTP in templates: headers look correct, but the page still loads HTTP assets.
  • Accidental redirect in a shared include: a global Nginx snippet or Apache include applies to every vhost.
  • Testing the wrong server: DNS hasn’t propagated or your resolver cached the old IP. Use --resolve while validating.

Summary: HTTPS redirect troubleshooting tutorial recap

Redirect loops and mixed content usually aren’t mysterious.

You’re dealing with duplicated rules, missing or ignored proxy headers, or a cached 301.

Map the hops with curl -IL, remove the extra redirect layer, then confirm the fix with a short acceptance checklist.

If you want a VPS where you can control Nginx/Apache, certificates, and redirects cleanly, start with a HostMyCode VPS. If you’d rather hand off the operational edge cases (including migrations and SSL), managed VPS hosting is the calmer option.

If you’re fixing HTTPS issues during a move, you’ll get better results by planning DNS, SSL, and server config together. HostMyCode can provision a clean server and help you migrate with fewer launch-day surprises through our HostMyCode VPS plans or fully assisted managed VPS hosting.

FAQ

How do I know if the redirect loop is from WordPress or the web server?

Temporarily disable application-level “force SSL” plugins, then test with curl -IL. If the loop persists, it’s coming from the server/CDN layer. If it stops, the issue was WordPress redirects, plugins, or URL settings.

Why does HTTPS work on my origin IP but fails behind Cloudflare?

Usually Cloudflare SSL mode is set to Flexible, or the origin doesn’t present a valid cert for the hostname. Switch to Full (strict) and confirm the origin certificate matches the domain.

Should I enable HSTS while troubleshooting redirects?

No. HSTS can pin browsers to HTTPS and make rollback painful. Enable it only after HTTPS is stable for the main domain and any required subdomains.

What’s the safest way to fix mixed content on WordPress?

Update site URLs to HTTPS, run a targeted WP-CLI search-replace (skipping GUID), then clear page caches and CDN caches. Finish by checking the browser console for blocked resources.

After fixing redirects, why do some users still see the old behavior?

Cached 301s. Have them test in a private window, purge CDN cache, and verify server response headers with curl -IL from multiple networks.