How to Detect and Fix HTTP 301 Redirect Loops on Apache

Changing a web page’s URL is risky: every existing link breaks, and search engines lose the ability to index the page. Apache’s redirection rules prevent that. Each rule flags the old URL, returns an HTTP 301 Moved Permanently response, and points to the new address. You define these rules in a .htaccess file.

A redirect loop happens when a redirected URL matches a rule that sends it right back to itself. At best, the loop eventually times out. At worst, it runs indefinitely. Here’s how to detect the problem, why it happens, and how to fix it.

Note: This article assumes that Apache is already installed on your Kamatera Ubuntu instance. For a detailed explanation of how to install Apache, see: Setting Up a LAMP Stack on Kamatera for Web Development, How to Install a LAMP Stack on Ubuntu.

Detection

Here are three ways to detect a redirect loop.

In the browser

Launch your browser and open a redirected page. If the browser can’t open the page, then you may have a redirect error.

Fixing .htaccess 301 Redirects on Apache

To see if the URL triggers a redirect loop, open your browser’s developer tools. In the console, search for ERR_TOO_MANY_REDIRECTS.

Fixing .htaccess 301 Redirects on Apache

Using curl

Curl is a CLI tool that sends a request to a URL and shows you the response. Here’s how to use it to detect redirect loops:

  • -I returns the HTTP header and status code
  • -L follows each redirect step, exposing the loop
  • –max-redirs <number> caps how many redirects curl will follow

Example:

HTTP/1.1 301 Moved Permanently
Location: https://mysite.com/
HTTP/2 301
Location: http://mysite.com/
HTTP/1.1 301 Moved Permanently
Location: https://mysite.com/

Apache error log

Apache logs all errors to a file. Open it in a text editor, or use tail to view the most recent entries at the end of the file:

tail -f /var/log/apache2/error.log

Fixing .htaccess 301 Redirects on Apache

Why redirect loops happen

Rule order

Rules execute in the order they’re defined. A general rule placed before a specific one can rewrite the specific rule’s target before it’s ever matched:

# General
RewriteRule ^products/(.*)$ /shop/$1 [R=301,L]
# Specific
RewriteRule ^products/old-product$ /products/new-product [R=301,L]

A loop is triggered because the modified specific URL no longer exists and can’t be matched.

File context

Say you have a small website in /var/www/html. This directory holds all the HTML pages that resolve to https://mysite, along with your .htaccess files. As the site grows, you group related pages into subdirectories. For example, all product pages are stored in /var/www/html/products, which resolves to https://mysite/products.

To keep redirect rules for that subdirectory manageable, you add a separate .htaccess file inside it. But when a rule inside that file starts with the ^/ pattern, Apache strips out the subdirectory name before matching:

RewriteRule ^/myproduct$ /products

The result: index.html still resolves correctly to the site’s home page, but other pages like products.html or product.html can no longer be located, which triggers a redirect loop.

URL canonicalization

A canonical URL has exactly one form, typically the www-prefixed, HTTPS version. Migrating in stages, for example HTTP to HTTPS first, then non-www to www, means multiple redirect rules firing in sequence. If any one of them points back to a version of the URL that no longer resolves, it loops:

RewriteEngine On
RewriteCond %{HTTPS} off [OR]
RewriteCond %{HTTP_HOST} !^www\. [NC]
RewriteRule ^ https://www.yourdomain.com%{REQUEST_URI} [L,R=301,NC]

Misplaced [L] flag

In .htaccess, the [L] flag doesn’t stop rule processing outright. It tells Apache to restart matching from the top of the ruleset using the newly rewritten URL. If that URL matches the same rule again, it loops indefinitely:

RewriteRule /myproducts /products [L]

How to fix redirect loops

Now that we know how to detect the problem and what causes it, we can fix it.

Rule order

Move specific rules above general ones:

# Specific
RewriteRule ^products/old-product$ /products/new-product [R=301,L]
# General
RewriteRule ^products/(.*)$ /shop/$1 [R=301,L]

Rule location and context

When a rule starts with the ^/ pattern, the subdirectory name is stripped out. To prevent this from happening, do not include this pattern in the path; for example, replace:

RewriteRule ^/myproduct$ /products

with

RewriteRule ^/myproduct$ /products

URL canonicalization

Combine your conditions into a single rule so the server resolves protocol and domain in one step, rather than redirecting through several intermediate URLs:

Misplaced [L] flag
Replace [L] with [END]. Unlike [L], [END] stops rule processing immediately rather than restarting it:
RewriteRule /myproducts /products [END]

Conclusion

Redirect loops happen because rule sets are easy to build up over time and hard to keep consistent. The fix is usually small: reorder a rule, drop a leading slash, or swap a flag. Catching it early keeps your pages indexable and your links working.

Have additional questions? Search below: