VPS Tutorials
How to Fix an HTTPS Redirect Loop on a VPS Website (2026): Cloudflare, Nginx, Reverse Proxy, and Certificate Configuration Troubleshooting
If your VPS website keeps redirecting and the browser reports too many redirects, it's usually Cloudflare SSL mode, Nginx forced HTTPS, reverse proxy headers, or the app's backend address fighting each other. Check layer by layer along the chain—don't just reinstall the certificate right away.

How to Fix an HTTPS Redirect Loop on a VPS Website (2026): Cloudflare, Nginx, Reverse Proxy, and Certificate Configuration Troubleshooting
Your site clearly has a certificate installed, but the moment you open it you get:
ERR_TOO_MANY_REDIRECTS
Or the browser keeps bouncing back and forth between http:// and https://, and after a few refreshes it won't load at all.
The scenario where I see this most often is: Nginx running on a VPS, Cloudflare in front, and behind that WordPress, the BT panel, a Docker reverse proxy, or some web application. Every layer thinks it should "help you redirect to HTTPS," and in the end they fight each other.
This article skips the mysticism and troubleshoots directly along the chain: browser -> Cloudflare/CDN -> Nginx -> reverse proxy -> application. Whichever layer is doing the duplicate redirect is the layer you fix.
Don't just look at the browser message. First use curl to see the redirect chain:
curl -I http://example.com
curl -I https://example.com
If you want to see the full redirect process:
curl -IL http://example.com
Focus on the Location of each hop:
HTTP/1.1 301 Moved Permanently
Location: https://example.com/
HTTP/2 301
location: http://example.com/
If you see it jump to https one moment and back to http the next, that's a classic loop.
I usually also add a DNS bypass test:
curl -I --resolve example.com:443:源站IP https://example.com
This helps you distinguish: is the problem at the Cloudflare/CDN layer, or is the origin VPS itself misconfigured?
If you use Cloudflare, check the SSL/TLS mode first.
The most dangerous combination is:
- Cloudflare set to Flexible
- Origin Nginx also forcing HTTP to HTTPS
Flexible means: the user-to-Cloudflare connection is HTTPS, but Cloudflare-to-your-VPS-origin uses HTTP.
So this happens:
- The user visits
https://example.com - Cloudflare requests the origin over
http://example.com - Nginx sees HTTP and redirects to HTTPS
- Cloudflare requests the origin over HTTP again
- Nginx redirects back to HTTPS again
- The loop begins
In this case, don't fiddle with Nginx for ages—first change Cloudflare SSL/TLS to:
Full
What's more recommended is:
Full (strict)
The prerequisite is a valid origin certificate. You can use Let's Encrypt or a Cloudflare Origin Certificate.
If your origin has no certificate yet, Flexible can get it running temporarily, but it's not suitable for long-term use. As long as your origin also does HTTPS redirects, loops are easy to trigger.
Nginx itself is also easy to write duplicate redirects into.
A common configuration is:
server {
listen 80;
server_name example.com www.example.com;
return 301 https://$host$request_uri;
}
This is fine—it means HTTP uniformly redirects to HTTPS.
But if you also write odd redirects inside the HTTPS server block, such as:
if ($scheme = http) {
return 301 https://$host$request_uri;
}
Or:
return 301 http://example.com$request_uri;
Things can go wrong.
First print out the full configuration:
sudo nginx -T | grep -n "return 301\|rewrite\|server_name\|listen"
Then test the configuration:
sudo nginx -t
A clean HTTPS site usually looks like this:
server {
listen 80;
server_name example.com www.example.com;
return 301 https://example.com$request_uri;
}
server {
listen 443 ssl http2;
server_name example.com www.example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
root /var/www/example.com;
index index.html index.php;
}
If you want to uniformly strip www, keep only one direction—don't have two directions redirecting to each other.
For example, unify to the bare domain:
server {
listen 443 ssl http2;
server_name www.example.com;
return 301 https://example.com$request_uri;
}
Don't also redirect back to www in the bare domain.
Many applications are deployed behind Nginx, such as:
用户 -> Nginx HTTPS -> Node / Django / Laravel / WordPress
The outside is already HTTPS, but the backend application receives HTTP forwarded by Nginx. If you don't tell the application "the original request was actually HTTPS," it may send another redirect itself.
When reverse proxying with Nginx, it's recommended to include these headers:
location / {
proxy_pass http://127.0.0.1:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
If Cloudflare is also in front, in some scenarios you can be more explicit:
proxy_set_header X-Forwarded-Proto https;
But don't change this blindly. Look at your chain first: if Nginx itself is the HTTPS entry point, $scheme is usually fine; if Cloudflare-to-origin is still HTTP, then go back to the SSL mode issue mentioned earlier.
The application layer must also trust the proxy headers.
For example, Laravel needs trusted proxies configured correctly; Django depends on SECURE_PROXY_SSL_HEADER; Express needs:
app.set('trust proxy', true)
Otherwise the application can't see the real protocol and may redirect erratically on its own.
WordPress is a hotspot for redirect loops.
First check the two addresses in the admin settings:
WordPress Address (URL)
Site Address (URL)
They should match the address you ultimately visit, for example:
https://example.com
If you can't get into the admin, query the database directly:
wp option get home
wp option get siteurl
If you don't have WP-CLI, query with MySQL:
SELECT option_name, option_value
FROM wp_options
WHERE option_name IN ('home', 'siteurl');
Fix:
wp option update home 'https://example.com'
wp option update siteurl 'https://example.com'
If you use caching plugins, security plugins, or SSL plugins, they may also perform duplicate HTTPS redirects. During troubleshooting you can temporarily disable plugins:
wp plugin deactivate --all
Once you confirm the problem is gone, re-enable them one by one.
Panels like BT, 1Panel, and Nginx Proxy Manager usually have a "force HTTPS" switch.
The problem is that many people enable it in three places at once:
- Cloudflare Always Use HTTPS
- Panel force HTTPS
- Application plugin force HTTPS
Each setting looks reasonable on its own, but stacked together they can loop.
My advice: keep only one layer responsible for the main redirect.
A relatively stable approach:
- Cloudflare uses Full/Full strict
- Origin Nginx handles HTTP -> HTTPS
- The application only recognizes the final site URL and doesn't redirect extra
If you want Cloudflare to handle the redirect, that's fine too, but then don't write a pile of duplicate rules on the origin. The key isn't which layer redirects—it's that multiple layers shouldn't compete.
Some sites have enabled HSTS, and the browser forcibly turns HTTP into HTTPS.
At that point, no matter how you test in the browser it looks like an automatic HTTPS redirect, but the server may not actually still be redirecting.
During troubleshooting, try to use:
curl -IL http://example.com
Rather than relying only on the browser.
You can also use incognito windows, switch browsers, or clear the HSTS cache.
In Chrome you can open:
chrome://net-internals/#hsts
Enter the domain to delete the local HSTS record.
However, if your site has already enabled HSTS publicly, don't casually change the live policy. Use curl first to determine the server's actual response.
When I hit an HTTPS redirect loop, I go in this order:
- Look at the redirect chain:
curl -IL http://example.com
curl -IL https://example.com
- If Cloudflare is used, check the SSL/TLS mode first:
Flexible -> 高风险
Full / Full strict -> 更合理
- Bypass the CDN to test the origin:
curl -I --resolve example.com:443:源站IP https://example.com
- Check the Nginx redirect rules:
sudo nginx -T | grep -n "return 301\|rewrite"
- Check the reverse proxy headers:
sudo nginx -T | grep -n "X-Forwarded-Proto\|proxy_set_header"
- Check the application backend URL, e.g. WordPress:
wp option get home
wp option get siteurl
- Temporarily turn off duplicate redirect layers, e.g. SSL plugins, panel force HTTPS, Cloudflare Always Use HTTPS.
| Symptom | Prime suspect | Where to look first |
|---|---|---|
| Loop after enabling Cloudflare Flexible | Protocol mismatch between Cloudflare and origin | SSL/TLS mode |
| HTTP and HTTPS redirect to each other | Nginx return/rewrite written backwards | nginx -T |
| Reverse-proxied app keeps redirecting to HTTPS | Backend doesn't know the original protocol | X-Forwarded-Proto |
| Can't get into WordPress admin | home/siteurl mismatch | wp_options |
| Only the browser redirects, curl is normal | HSTS or browser cache | Incognito, HSTS cache |
| Multiple forced HTTPS enabled | Duplicate redirects across layers | CDN, panel, application plugins |
The most annoying thing about HTTPS redirect loops is: every layer looks "securely configured."
Cloudflare wants to help you redirect to HTTPS, Nginx wants to help you redirect to HTTPS, and the WordPress plugin wants to help you redirect to HTTPS too. The result isn't more security—it's a fight.
When troubleshooting, don't reissue the certificate right away, and don't reload Nginx over and over. First print out the redirect chain and see clearly which layer is redirecting the request from where to where.
As long as you remember one sentence, you basically won't go wrong:
Across the whole chain, let only one layer handle the main HTTP-to-HTTPS redirect; the other layers should identify the real protocol and not duplicate the work.