Errors / Thursday September 17, 2026
How to Find and Fix Misconfigured Redirect Rules

Redirect rules are some of the most powerful tools in web server configuration. They canonicalize URLs, enforce HTTPS, handle domain migrations, and route traffic exactly where it needs to go. But a single misconfigured rule can create a loop that takes your entire site down. The browser follows the chain until it gives up and throws ERR_TOO_MANY_REDIRECTS, and you’re left trying to figure out which of your many rules is fighting with which other rule.
This guide walks through the most common misconfiguration patterns across Apache .htaccess, Nginx, and PHP, shows you exactly what the broken rules look like, and explains how to fix them correctly.
How Redirect Rule Errors Create Loops
Before getting into specific fixes, it helps to understand the pattern. A redirect loop happens when:
- A rule redirects Request A to URL B.
- URL B satisfies the same condition as Request A.
- The rule fires again, redirecting back to something that satisfies the same condition again.
- The cycle repeats until the browser stops following redirects and reports an error. Redirect limits vary by client.
The most frequent cause of the “too many redirects” error is a misconfigured setting in the .htaccess file. Conflicting redirects on the server side are among the top causes.
The tricky part is that rules rarely look broken in isolation. The problem usually only appears when two rules interact, or when a server-level rule conflicts with a CMS-level rule doing the same job in a different way.
Diagnosing Which Rule Is Causing the Loop
Before editing any files, trace the actual redirect chain so you know exactly which URLs are bouncing and where the loop forms.
Using curl on the Command Line
bash
curl -I -L –max-redirs 10 https://yourdomain.com
This follows every redirect and prints the status code and destination for each hop. If you see the same URL appear twice, you’ve found your loop.
Using browser DevTools
Open the Network tab in your browser, check Preserve Log, and load the URL. Every redirect will appear as a separate row. Look for any URL that appears more than once in the chain.
Isolating the source
If you’re unsure which file is causing the loop, try renaming your .htaccess file. If the error stops when doing so, something in that file is causing the loop itself.
This is the fastest way to confirm whether the .htaccess is the culprit before spending time reading through individual rules.
Apache .htaccess: The Most Common Misconfiguration Patterns
Conditions That Always Evaluate to True
The most frequent .htaccess mistake is writing a RewriteCond that never excludes the destination URL, meaning the rule fires on the destination just as readily as the source, creating an infinite loop.
For example, if you try to redirect domain.com to a subdomain my.domain.com with a condition checking for domain.com in the host, the condition will always be true because the subdomain also contains domain.com.
Broken rule:
apache
RewriteCond “%{HTTP_HOST}” “domain.com$”
RewriteRule “.*” “https://my.domain.com/” [L,R=301]
Correct rule:
apache
RewriteCond %{HTTP_HOST} !^my\.domain\.com [NC]
RewriteRule ^(.*)$ https://my.domain.com/$1 [R=301,L]
The ! negation in the condition is what breaks the loop. It tells Apache: do not fire this rule if the host is already my.domain.com.
www to Non-www (or Vice Versa) Loops
A www/non-www loop occurs when two rules enforce opposite canonical hosts. For example, one rule sends example.com to www.example.com, while another sends www.example.com back to example.com
Broken rule:
apache
RewriteCond %{HTTP_HOST} ^example.com$ [NC]
RewriteRule ^ https://www.example.com%{REQUEST_URI} [R=301,L]
RewriteCond %{HTTP_HOST} ^www.example.com$ [NC]
RewriteRule ^ https://example.com%{REQUEST_URI} [R=301,L]
Correct rule:
apache
RewriteCond %{HTTP_HOST} ^example\.com$ [NC]
RewriteRule ^ https://www.example.com%{REQUEST_URI} [R=301,L]
HTTP to HTTPS Loops
The HTTP-to-HTTPS redirect is one of the most common .htaccess rules, and one of the most frequently broken. Duplicate HTTPS enforcement is redundant but does not automatically create a loop. A loop occurs when different layers disagree about the final scheme or host, or when a reverse proxy terminates HTTPS and the backend incorrectly interprets the original request as HTTP.
Correct single-layer HTTPS redirect:
apache
RewriteEngine On
RewriteCond %{HTTPS} off
RewriteRule (.*) https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]
Forcing all traffic over HTTPS is a good practice, but using multiple methods to do it can cause a redirect loop. The best approach is to use one central rule and remove all others.
If your hosting panel has a “Force HTTPS” toggle, use that and remove any equivalent rule from your .htaccess. Avoid enforcing the same redirect in multiple layers because it complicates troubleshooting and can create loops when their destinations or protocol detection differ.
Pro Tip: If you use a reverse proxy or load balancer in front of your server (like Cloudflare or a CDN), the actual request may arrive at Apache over HTTP even though the visitor connected over HTTPS. In that case, checking %{HTTPS} off will always be true and will loop. Check %{HTTP:X-Forwarded-Proto} instead, which reflects what the visitor actually used.
Rule Order and the [L] Flag
Apache processes .htaccess rules from top to bottom, and without the [L] (Last) flag, it will continue processing subsequent rules even after a match. This can mean a request gets redirected multiple times in a single pass.
For external redirects in .htaccess, pairing [R] with [L] normally prevents later rewrite rules from running during the current rewrite pass. On Apache 2.4+, [END] can be used when you need to terminate per-directory rewrite processing entirely. And if you have both a www redirect and an HTTPS redirect in the same file, think carefully about their order – the output of the first rule must not match the condition of the second.
Safe combined order:
apache
# Step 1: redirect non-www to www
RewriteCond %{HTTP_HOST} !^www\. [NC]
RewriteRule ^(.*)$ https://www.%{HTTP_HOST}%{REQUEST_URI} [R=301,L]
# Step 2: redirect HTTP to HTTPS (only fires if already on www)
RewriteCond %{HTTPS} off
RewriteRule (.*) https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]
Resetting the .htaccess File
If the rules have become too tangled to untangle by reading, reset the file to its WordPress default and rebuild from there.
To reset your .htaccess file, rename the existing file (for example, to .htaccess_backup), then create a new file named .htaccess and add the default WordPress code.
apache
# BEGIN WordPress
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteRule .* – [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
RewriteBase /
RewriteRule ^index\.php$ – [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php [L]
</IfModule>
# END WordPress
If WordPress can write to .htaccess, opening Settings > Permalinks and saving the permalink settings regenerates WordPress’s rewrite rules. Preserve any required custom rules separately before doing this.
Pro Tip: Always download a backup of your .htaccess before making any edits. It only takes a moment and means you have a safe rollback point if your changes break something.
Nginx: Rewrite and Return Directive Loops
Nginx handles redirects differently from Apache. There is no .htaccess equivalent – rules live in the server configuration files, typically at /etc/nginx/nginx.conf or /etc/nginx/conf.d/. This makes them more performant but also means you need server access to edit them.
The Most Common Nginx Loop: HTTP to HTTPS
One of the most common mistakes in Nginx is duplicating redirect logic. The correct approach is to define a single, clean redirect rule at the HTTP level and avoid repeating it elsewhere.
Correct Nginx HTTPS redirect:
nginx
server {
listen 80;
server_name example.com www.example.com;
return 301 https://example.com$request_uri;
}
server {
listen 443 ssl;
server_name example.com;
# serve content here — no redirect back to HTTP
}
The HTTPS server block must not contain any redirect back to HTTP or any condition that would re-trigger the HTTP block. That combination creates an immediate loop.
Using return vs rewrite for Redirects
For URL canonicalization such as HTTP to HTTPS, use return instead of rewrite. It is more efficient and clearer, and avoids unnecessary internal redirect processing.
For simple client redirects such as HTTP-to-HTTPS or canonical-host redirects, return is usually clearer and simpler. A rewrite directive can either alter the URI internally or send a client redirect depending on its flags. For example, permanent returns a 301, while last starts a new location search for the rewritten URI.
Avoid:
nginx
rewrite ^(.*)$ https://example.com$1 permanent;
Use instead:
nginx
return 301 https://example.com$request_uri;
The try_files Internal Redirect Loop
try_files is commonly used in Nginx to route requests to an existing file or directory before falling back to another URI. If the fallback is misconfigured and keeps being processed by the same redirect logic, it can create an internal redirect loop.
Nginx limits internal redirects to 10 cycles per request. If that limit is exceeded, Nginx stops processing the request and returns an error.
For a typical single-page application (SPA), use a simple try_files configuration:
location / {
try_files $uri $uri/ /index.html;
}
This tells Nginx to serve the requested file or directory when it exists and fall back to /index.html when it does not.
Make sure /index.html actually exists and is not itself rewritten back into the same cycle. Otherwise, the fallback can repeatedly trigger the same routing logic and result in an internal redirect loop.
Testing and Reloading Nginx Config
Always test configuration syntax before reloading:
bash
sudo nginx -t
If the test passes:
bash
sudo nginx -s reload
For routine configuration changes, validate with nginx -t and prefer a graceful reload. Restart the service only when your environment or change specifically requires it.
Pro Tip: Set LogLevel to trace4 temporarily in Apache, or enable error_log at debug level in Nginx to see full rewrite traces. This reveals exactly which rules are firing and in what order, making loop diagnosis much faster. Remember to revert the log level once you’re done.
PHP Redirect Loops
PHP redirects using header(‘Location: …’) are another common source of loops, particularly when the redirect logic depends on conditions that can be true on the destination URL as well as the source.
Redirects That Point Back to Themselves
php
// Broken: redirects to HTTPS, but if already on HTTPS this still fires
if ($_SERVER[‘HTTP_HOST’] === ‘example.com’) {
header(‘Location: https://example.com/’);
exit;
}
If this code runs on every page load without checking whether the redirect condition is already satisfied, it will loop.
Fixed version:
php
if (empty($_SERVER[‘HTTPS’]) || $_SERVER[‘HTTPS’] === ‘off’) {
header(‘Location: https://’ . $_SERVER[‘HTTP_HOST’] . $_SERVER[‘REQUEST_URI’], true, 301);
exit;
}
Missing exit After header()
A PHP redirect without an exit statement after it is a bug that can cause unpredictable behavior, including redirect loops. Without exit, PHP continues executing the rest of the script, which may itself trigger another redirect.
php
// Broken
header(‘Location: https://example.com/new-page/’);
// Script continues running here and may redirect again
// Correct
header(‘Location: https://example.com/new-page/’);
exit;
PHP Redirects Behind a Proxy or Load Balancer
When Nginx (or any proxy) is acting as a reverse proxy, missing headers can confuse the backend application into thinking the request is still HTTP. The application then tries to redirect to HTTPS again, creating a loop. The X-Forwarded-Proto header is especially important, as it tells the application whether the original request was HTTP or HTTPS.
In PHP, the correct way to check the original protocol behind a proxy is:
php
$protocol = (!empty($_SERVER[‘HTTP_X_FORWARDED_PROTO’]))
? $_SERVER[‘HTTP_X_FORWARDED_PROTO’]
: ((!empty($_SERVER[‘HTTPS’]) && $_SERVER[‘HTTPS’] !== ‘off’) ? ‘https’ : ‘http’);
if ($protocol !== ‘https’) {
header(‘Location: https://’ . $_SERVER[‘HTTP_HOST’] . $_SERVER[‘REQUEST_URI’], true, 301);
exit;
}
This correctly reads the forwarded protocol rather than the internal server variable, which would always show HTTP behind a proxy.
WordPress-Specific Rule Conflicts
URL Settings Mismatch
WordPress stores its own URL settings in the database, separate from whatever your server config says. If these two disagree, WordPress will enforce a redirect to its configured URL while the server may enforce a redirect to something different, and the two rules fight.
Check and align your URL settings by adding these lines to wp-config.php:
php
define(‘WP_HOME’, ‘https://yourdomain.com’);
define(‘WP_SITEURL’, ‘https://yourdomain.com’);
These values must match exactly (same protocol, same www/non-www format) across wp-config.php, the WordPress database (Settings > General), your .htaccess, and any HTTPS enforcement at the server level.
Redirect Plugins Conflicting with Server Rules
If you have a redirect plugin installed (like the Redirection plugin or a security plugin with URL enforcement), it may be adding its own redirect rules that duplicate or contradict your server-level rules.
Since plugin conflicts are a common cause, disable all plugins to test: via the admin dashboard if accessible, or via FTP by renaming the wp-content/plugins folder to something like plugins_disabled. If the error disappears, a plugin was responsible. Reactivate plugins one by one and test after each.
Pro Tip: Keep redirect logic in one place. Either enforce HTTPS and canonical URLs at the server level (.htaccess or Nginx config) and remove all plugin-based redirect rules, or manage all redirects through a single plugin and remove server-level rules. Splitting redirect responsibility across multiple layers is the primary reason these conflicts happen.
Best Practice: Keep Each Redirect Responsibility in One Layer
The most effective fix for redirect loop errors caused by misconfigured rules is to simplify the setup, define one clear redirect strategy, and ensure every component follows it. A stable configuration is not about adding more rules — it is about removing unnecessary ones and making each layer behave predictably.
For most sites, the correct architecture is:
- Server level (.htaccess or Nginx config): handles HTTP to HTTPS and www/non-www canonicalization
- CMS level (WordPress settings): reflects the canonical URL but does not add additional redirect rules
- Plugin level: handles only content redirects (old URLs to new URLs after restructuring) — never protocol or host redirects
If each layer has a clearly defined, non-overlapping scope, redirect loops become almost impossible.
Frequently Asked Questions
The fastest test is to rename the file temporarily (for example, to .htaccess_backup) and reload the page. If the redirect loop stops, the .htaccess file contains the problematic rule. You can then restore the file and work through it section by section, commenting out rules until the loop reappears to identify the exact culprit.
Yes, and this is one of the more difficult scenarios to debug because neither rule looks wrong in isolation. The most common version is a server-level HTTPS redirect in .htaccess combined with a plugin that also forces HTTPS using PHP headers or its own redirect logic. The solution is to pick one approach and remove the other entirely.
Both can create redirects, but they work differently. Redirect is a simpler directive for basic URL-to-URL redirects and is handled by Apache’s mod_alias module. RewriteRule is more powerful and pattern-based, handled by mod_rewrite, and allows conditions (RewriteCond) and regex matching. Using both for the same URL can cause conflicts. If you are using RewriteRule for redirect management, avoid mixing in standalone Redirect directives for the same paths.
This usually means the www and non-www server blocks have different redirect logic, or one block is missing a rule the other has. Check that both www.example.com and example.com are handled by server blocks that send traffic to the same final destination, and that the final destination server block does not contain any redirect back toward either of those forms.