Resolving Apache .htaccess Redirect Loop 500 Internal Server Error
Fix Apache .htaccess redirect loops causing 500 errors. Learn to troubleshoot mod_rewrite rules, canonical URLs, and SSL configurations to restore site access quickly.
A “500 Internal Server Error” originating from an .htaccess redirect loop is a common, yet frustrating, issue for web administrators. It typically signifies a misconfigured mod_rewrite rule that continuously redirects a request, causing the Apache server to exceed its internal redirect limit and eventually fail. This guide will walk you through diagnosing and resolving these persistent redirect loops, ensuring your website serves content correctly.
Symptom & Error Signature
When an Apache .htaccess redirect loop occurs, users will typically encounter one of the following in their web browser:
- HTTP 500 Internal Server Error: A generic error page indicating a server-side problem.
- ERR_TOO_MANY_REDIRECTS: Some browsers, like Chrome, explicitly report that the page isn’t redirecting properly.
- “This page isn’t working” / “Firefox has detected that the server is redirecting the request for this address in a way that will never complete.”: Similar messages indicating a browser-level redirect limit was hit.
The definitive proof of a redirect loop resides in your Apache error logs. You’ll typically find an entry similar to this:
[Sat Jun 27 10:30:00.123456 2026] [core:error] [pid 12345:tid 123456789] [client 192.168.1.100:54321] AH00124: Request exceeded the limit of 10 internal redirects due to probable configuration error. Use 'LimitInternalRecursion' to increase the limit if necessary. Use 'LogLevel Debug' to get a backtrace.
[Sat Jun 27 10:30:00.123456 2026] [core:error] [pid 12345:tid 123456789] [client 192.168.1.100:54321] AH00124: /var/www/html/index.php pcfg_openfile: unable to check access time of /var/www/html/index.php
The AH00124 error code is the primary indicator of an internal redirect loop. The message might also point to the specific file being repeatedly requested, like index.php.
Root Cause Analysis
A redirect loop essentially means that a RewriteRule in your .htaccess file is continuously matching the URL it just rewrote, or it’s part of a cycle of two or more rules rewriting back and forth. This leads to an endless internal redirection chain until Apache’s LimitInternalRecursion (default 10) is hit, triggering the 500 error.
Common underlying reasons include:
- Missing or Incorrect
RewriteCond(Rewrite Condition): Rules are applied indiscriminately without checking if the target condition (e.g., already HTTPS, already non-WWW) is met. This is the most frequent culprit. - Conflicting
RewriteRuleDirectives: Two or more rules that contradict each other, leading to an infinite cycle (e.g., A redirects to B, and B redirects back to A). - Improper Use of Flags (
[L],[R],[NC],[OR]):- Lack of
[L](Last): Allows Apache to continue processing subsequent rules even after a match, potentially re-matching the same rule or a conflicting one. - Incorrect
[R](Redirect): Causes an external HTTP redirect instead of an internal rewrite, which can also loop if not properly conditioned.
- Lack of
RewriteBaseMisconfiguration: In subdirectory installations, an incorrectRewriteBasecan lead to URI mismatches, causing rules to re-evaluate incorrectly.- Environment Mismatches (e.g., SSL offloading with proxies): When your website is behind a reverse proxy or load balancer that handles SSL, Apache might not see
%{HTTPS}ason. If you then force HTTPS based on%{HTTPS}, it will loop. - Caching Issues: Less common for a 500 error, but browser or CDN caches can sometimes store faulty redirects, exacerbating testing.
AllowOverrideDirective: While usually resulting in a 403 Forbidden or rules not being applied, ifAllowOverrideis set toNoneand an old browser cache still tries to apply a redirect, it could contribute to confusion. However, for a 500AH00124, the.htaccessfile is being read and processed.
Step-by-Step Resolution
Follow these steps to diagnose and resolve the Apache .htaccess redirect loop.
1. Backup Your .htaccess File
[!IMPORTANT] Before making any changes, always back up your current
.htaccessfile. This allows you to revert to the original state if things go wrong or if you make an incorrect modification.
You can do this via SSH:
sudo cp /var/www/html/.htaccess /var/www/html/.htaccess.backup_$(date +%Y%m%d%H%M%S)
2. Locate and Examine Apache Error Logs
The Apache error logs are your primary source of information. On Debian/Ubuntu systems, they are typically found here:
sudo tail -f /var/log/apache2/error.log
Keep this terminal window open while you attempt to access your website. Look for the AH00124 error message. It may provide clues about the specific URL or file that’s being repeatedly accessed.
3. Temporarily Disable mod_rewrite or Comment Out Rules
To quickly confirm if mod_rewrite is indeed the cause, you can temporarily disable it or comment out all RewriteRule and RewriteCond directives.
-
Disable
mod_rewrite(less recommended for.htaccessissues): This requires root access and will affect allmod_rewriterules on the server.sudo a2dismod rewrite sudo systemctl restart apache2If your site loads (without redirects, potentially looking broken),
mod_rewritewas the issue. Re-enable withsudo a2enmod rewriteafter troubleshooting. -
Comment out
.htaccessrules (recommended): Edit your.htaccessfile (e.g.,nano /var/www/html/.htaccess) and place a#at the beginning of everyRewriteRuleandRewriteCondline.#RewriteEngine On #RewriteCond %{HTTPS} off #RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301] #RewriteCond %{HTTP_HOST} ^www\.example\.com [NC] #RewriteRule ^(.*)$ https://example.com/$1 [L,R=301]Save the file and try accessing your site. If the 500 error disappears, the issue is definitely within your
mod_rewriterules. Re-enable rules one by one or in small groups to pinpoint the problematic one.
4. Analyze and Correct Common Redirect Loop Scenarios
Once you’ve identified the problematic rule (or set of rules), apply the following common fixes:
Scenario A: HTTP to HTTPS Redirect Loop
This is often caused by the RewriteCond %{HTTPS} off not correctly detecting HTTPS when behind a reverse proxy or load balancer.
Problematic Example:
RewriteEngine On
RewriteCond %{HTTPS} off
RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
If your server is behind a proxy that terminates SSL, Apache might still see requests as HTTP (%{HTTPS} is off), even though the user connected via HTTPS. The proxy typically sets an X-Forwarded-Proto header.
Solution: Check for X-Forwarded-Proto header.
# Correct HTTP to HTTPS redirect, handling proxies
RewriteEngine On
RewriteCond %{HTTP:X-Forwarded-Proto} !https [NC]
RewriteCond %{HTTPS} off
RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
The [NC] flag makes the condition case-insensitive. Adding %{HTTPS} off as an OR condition, or as a fallback, is also a robust approach for environments without proxies.
Scenario B: WWW to Non-WWW (or vice-versa) Redirect Loop
Loops can occur when combined with HTTPS redirects, or if the rule isn’t specific enough.
Problematic Example (forcing non-WWW):
RewriteEngine On
# Force non-WWW (might loop with HTTPS or other rules)
RewriteCond %{HTTP_HOST} ^www\.example\.com [NC]
RewriteRule ^(.*)$ http://example.com/$1 [L,R=301]
If you have a separate HTTP to HTTPS rule and then this one, they might conflict, e.g., https://www.example.com -> http://example.com (by this rule) -> https://example.com (by HTTPS rule), leading to a loop.
Solution: Combine redirects and ensure conditions prevent self-matching.
# Combined HTTPS and non-WWW redirect (canonical URL)
RewriteEngine On
RewriteCond %{HTTP:X-Forwarded-Proto} !https [NC,OR]
RewriteCond %{HTTPS} off [OR]
RewriteCond %{HTTP_HOST} ^www\.example\.com [NC]
RewriteRule ^(.*)$ https://example.com%{REQUEST_URI} [L,R=301]
This single rule ensures all requests are sent to https://example.com, removing both the www. and forcing HTTPS in one go, preventing conflicts. Change example.com to your domain.
Scenario C: Trailing Slash Issues
Redirecting URLs to add/remove trailing slashes can loop if not handled carefully, especially with DirectoryIndex or file names.
Problematic Example:
# Add trailing slash for directories (problem if target is already /)
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule ^(.*[^/])$ $1/ [R=301,L]
Solution: Be specific and use conditions to avoid matching already correct URLs.
# Add trailing slash if a directory AND no slash exists
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule ^(.*[^/])$ /$1/ [R=301,L]
# Or, remove trailing slash if not a directory (e.g. for clean URLs)
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_URI} (.+)/$
RewriteRule ^(.*)/$ $1 [R=301,L]
Scenario D: Index File Redirects (e.g., index.php removal)
If you’re rewriting /index.php to /, ensure it doesn’t then try to serve /index.php again implicitly.
Problematic Example:
RewriteRule ^(.*)/index\.php$ /$1/ [R=301,L]
If Apache’s DirectoryIndex is set to index.php, requesting / will internally map to index.php, which then matches this rule, leading to a loop.
Solution: Use conditions to avoid matching the base URL after the rewrite, or ensure your DirectoryIndex is well-defined.
# Remove index.php from URL, but prevent loop on root
RewriteCond %{THE_REQUEST} /index\.php [NC]
RewriteRule ^(.*)index\.php$ /$1 [R=301,L,NC]
%{THE_REQUEST} matches the initial request line, preventing internal rewrites from triggering the rule again.
5. Ensure [L] (Last) Flag is Used Appropriately
The [L] (Last) flag is crucial. It tells mod_rewrite to stop processing the current set of rules if the RewriteRule matches. Without it, subsequent rules in .htaccess might re-evaluate the rewritten URL, potentially leading to a loop. Ensure all final redirect rules ([R=301]) also include [L].
6. Verify AllowOverride Directive
For .htaccess files to work, the AllowOverride directive in your Apache virtual host configuration or apache2.conf must permit it. If AllowOverride is set to None for the relevant directory, your .htaccess rules will be ignored (often resulting in a 403 Forbidden or no redirect at all), but in some edge cases, it can cause unexpected behavior.
[!IMPORTANT] The
AllowOverridedirective is configured in your main Apache configuration files, NOT in.htaccess. Common locations are/etc/apache2/apache2.confor in your site’s virtual host configuration file (e.g.,/etc/apache2/sites-available/yourdomain.conf).
Open your virtual host configuration file:
sudo nano /etc/apache2/sites-available/yourdomain.conf
Or the main Apache config:
sudo nano /etc/apache2/apache2.conf
Look for a <Directory> block corresponding to your website’s root path (e.g., /var/www/html). Ensure AllowOverride is set to All or FileInfo. FileInfo is generally sufficient for mod_rewrite rules.
<Directory /var/www/yourwebsite>
Options Indexes FollowSymLinks
AllowOverride All # Or AllowOverride FileInfo
Require all granted
</Directory>
If you modify your main Apache configuration, you’ll need to restart Apache.
7. Test Configuration and Restart Apache
After making any changes to .htaccess or main Apache configuration, always test the syntax and restart the server.
sudo apache2ctl configtest
You should see Syntax OK. If not, review the error messages and correct them.
sudo systemctl restart apache2
8. Clear Browser Cache
[!WARNING] Web browsers aggressively cache 301 (Permanent) redirects. If you’ve been testing faulty redirect rules, your browser might have cached the bad redirect, causing it to loop even after you’ve fixed the
.htaccessfile.
After fixing your .htaccess file and restarting Apache, clear your browser’s cache completely, or use an incognito/private browsing window to test. You can also use curl -I example.com from the command line to see HTTP headers and verify redirects without browser caching.
curl -I https://example.com
curl -I http://www.example.com
This will show you the exact HTTP response codes (e.g., HTTP/1.1 301 Moved Permanently) and the Location header, indicating where the server is attempting to redirect.
By systematically applying these steps, you should be able to identify, correct, and resolve the Apache .htaccess redirect loop leading to a 500 Internal Server Error.
