Troubleshoot and fix Apache 500 Internal Server Errors caused by infinite redirect loops in .htaccess files on Ubuntu 20.04 LTS servers.
When managing web servers, few issues are as frustrating and immediately impactful as a "500 Internal Server Error" due to an Apache .htaccess redirect loop. This common problem indicates that your Apache server, specifically its mod_rewrite module, is caught in an endless cycle of redirecting requests, eventually hitting an internal limit and failing. This guide provides a highly technical, step-by-step approach to diagnose and resolve this issue on an Ubuntu 20.04 LTS server running Apache 2.4.
Symptom & Error Signature
Users attempting to access your website will typically encounter one of the following:
- Browser Output (Common):
- * "500 Internal Server Error"
- * "ERRTOOMANY_REDIRECTS" (e.g., in Chrome)
- * "The page isn't redirecting properly" (e.g., in Firefox)
- * A blank white page, sometimes after a long loading time.
- Apache Error Log Output (
/var/log/apache2/error.log): - You will likely see recurring entries indicating a redirect limit being exceeded. The exact message may vary slightly but will be similar to this:
[Timestamp] [proxy:error] [pid XXXX:tid YYYYYYYYYYY] (X) [client IP_ADDRESS:PORT] AH01144: 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.
[Timestamp] [core:crit] [pid XXXX:tid YYYYYYYYYYY] [client IP_ADDRESS:PORT] 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.
```
You might also see `mod_rewrite` related debug messages if `LogLevel` is increased:
```
[Timestamp] [rewrite:trace1] [pid XXXX:tid YYYYYYYYYYY] [client IP_ADDRESS:PORT] AH00666: [client IP_ADDRESS:PORT] (3) [main/htaccess] applying pattern '^$' to uri '/index.php'
[Timestamp] [rewrite:trace1] [pid XXXX:tid YYYYYYYYYYY] [client IP_ADDRESS:PORT] AH00669: [client IP_ADDRESS:PORT] (3) [main/htaccess] rewrite '/index.php' -> '/index.php'
```
Root Cause Analysis
A 500 Internal Server Error caused by a redirect loop in .htaccess primarily stems from mod_rewrite rules that inadvertently create an infinite chain of redirects or rewrites. The underlying reasons typically fall into these categories:
- Infinite Rewrite Loop: The most common culprit. A set of
RewriteRuleorRewriteConddirectives are configured such that a request, after being processed by one rule, is then matched again by the same rule or another rule that effectively sends it back to a state where the first rule applies again. This cycle continues until Apache's internal redirect limit (defaulting to 10 for internal rewrites) is exceeded, leading to the 500 error.
- Incorrect
RewriteBaseDirective: When.htaccessfiles are used in subdirectories,RewriteBaseis crucial for correctly calculating the path for internal rewrites. An incorrectly set or missingRewriteBasecan causemod_rewriteto append the subdirectory path repeatedly, leading to an infinite loop as the URI keeps growing.
- Missing or Misplaced
[L](Last) Flag: The[L]flag tellsmod_rewriteto stop processing furtherRewriteRuledirectives for the current request after a match. If this flag is omitted where needed, subsequent rules might re-evaluate the already rewritten URI, potentially sending it into a loop.
- Conflicting or Poorly Ordered Rules: When combining multiple redirect types (e.g., HTTP to HTTPS, www to non-www, adding/removing trailing slashes), the order of the rules is paramount. If, for instance, a non-www to www rule redirects to HTTP, and an HTTP to HTTPS rule then redirects to HTTPS, but doesn't correctly handle the www/non-www part, a loop can form.
- Proxy/Load Balancer Awareness: If Apache is behind a reverse proxy or load balancer (e.g., Nginx, AWS ELB/ALB), the
mod_rewriterules for HTTPS detection (RewriteCond %{HTTPS} off) might fail. The proxy terminates SSL and forwards requests to Apache via HTTP, making%{HTTPS}always appearoff. This causes Apache to continually redirect to HTTPS, even if the user is already on HTTPS externally, creating a loop. TheX-Forwarded-Protoheader is crucial here.
- Syntactic Errors in
modrewriteDirectives: While less common for a redirect loop and more for a general 500 error, malformed regular expressions or incorrectmodrewritesyntax can lead to unpredictable behavior, including loops if the engine misinterprets the rules.
Step-by-Step Resolution
Follow these steps meticulously to diagnose and resolve your Apache .htaccess redirect loop.
1. Verify mod_rewrite is Enabled and AllowOverride is Set
First, ensure the mod_rewrite module is enabled and that Apache is configured to allow .htaccess overrides for your web directory.
- Check
mod_rewritestatus: -
`bash - sudo apache2ctl -M | grep rewrite
-
` - If
rewrite_moduleis not listed, enable it: -
`bash - sudo a2enmod rewrite
- sudo systemctl restart apache2
-
`
- Check
AllowOverridedirective: - The Apache configuration for your virtual host or directory must permit
.htaccessoverrides. Open your virtual host configuration file, typically located at/etc/apache2/sites-available/your-domain.confor the main Apache configuration/etc/apache2/apache2.conf. - Locate the
<Directory>block for your web root (e.g.,/var/www/html/):
<Directory /var/www/html/>
Options Indexes FollowSymLinks
AllowOverride All # This MUST be All
Require all granted
</Directory>
```
> [!IMPORTANT]
> `AllowOverride All` is crucial for `.htaccess` files to be processed. If it's set to `None` or not explicitly defined, your `.htaccess` rules will be ignored or could lead to other issues. After modification, restart Apache:
```bash
sudo systemctl restart apache2
2. Inspect Apache Error Logs for Clues
The Apache error log is your primary source of diagnostic information. It will often explicitly state that a redirect limit has been exceeded.
- Monitor the error log in real-time:
-
`bash - sudo tail -f /var/log/apache2/error.log
-
` - Attempt to access the problematic URL in your browser.
- Observe the log output. Look for messages similar to
Request exceeded the limit of 10 internal redirects. This confirms a rewrite loop. The log might also indicate the specific path being repeatedly rewritten.
3. Temporarily Disable the .htaccess File
To confirm that the .htaccess file is indeed the source of the problem, temporarily disable it.
- Navigate to your website's document root:
-
`bash - cd /var/www/html/your-domain.com/public_html/ # Adjust path as necessary
-
` - Rename the
.htaccessfile: -
`bash - mv .htaccess .htaccess_bak
-
` - Clear your browser cache (Ctrl+Shift+R or Cmd+Shift+R) and try accessing your site again.
- * If the site now loads (even without intended redirects), then the
.htaccessfile is definitely the cause. - * If the site still shows a 500 error or doesn't load, the issue might be elsewhere (e.g., PHP error, server misconfiguration), but for this guide, we assume the
.htaccessis the culprit. - Once confirmed, proceed to analyze the
.htaccesscontents. You can rename it back:mv .htaccessbak .htaccessbefore proceeding to the next step, or work on.htaccessbakdirectly and then rename it.
4. Analyze and Correct .htaccess Rules
This is the most critical step. You'll need to carefully review your .htaccess file for common misconfigurations that lead to redirect loops.
[!IMPORTANT] Always back up your
.htaccessfile before making any changes. A simplecp .htaccess .htaccessbackup$(date +%Y%m%d%H%M%S)will suffice.
Open your .htaccess file for editing:
`bash
nano .htaccess # Or your preferred editor
`
Focus on RewriteEngine On, RewriteCond, and RewriteRule directives.
Common Pitfalls and Solutions:
a. HTTP to HTTPS Redirect Loop (Especially with Load Balancers/Proxies)
This is a very common loop, often occurring when Apache is behind a proxy that handles SSL termination. Apache then sees requests as HTTP, and the .htaccess repeatedly tries to redirect to HTTPS.
Incorrect (Common Error):
`apacheconf
# This can loop if Apache sees all requests as HTTP due to a proxy
RewriteEngine On
RewriteCond %{HTTPS} off
RewriteRule ^(.*)$ https://%{HTTPHOST}%{REQUESTURI} [L,R=301]
`
Corrected using X-Forwarded-Proto (for proxy setups):
If your proxy sets X-Forwarded-Proto (e.g., https), use this header to detect the original protocol.
`apacheconf
RewriteEngine On
RewriteCond %{HTTP:X-Forwarded-Proto} !https [NC]
RewriteRule ^ https://%{HTTPHOST}%{REQUESTURI} [L,R=301]
`
If not behind a proxy and direct SSL:
`apacheconf
RewriteEngine On
RewriteCond %{HTTPS} off
RewriteRule ^ https://%{HTTPHOST}%{REQUESTURI} [L,R=301]
`
b. WWW to Non-WWW (or vice versa) Redirect Loop
Ensure your www/non-www rules are correctly ordered, especially when combined with HTTPS redirects.
Incorrect (Potential Loop/Conflict):
`apacheconf
# This rule might redirect to HTTP www, then HTTPS rule kicks in, then back to HTTP www…
RewriteEngine On
RewriteCond %{HTTP_HOST} ^www.(.*)$ [NC]
RewriteRule ^(.*)$ http://example.com/$1 [L,R=301]
`
Corrected (Non-WWW with HTTPS priority): It's generally best to redirect to HTTPS first, then handle www/non-www.
1. Redirect HTTP to HTTPS RewriteCond %{HTTPS} off RewriteRule ^ https://%{HTTPHOST}%{REQUESTURI} [L,R=301]
2. Redirect WWW to Non-WWW (only if the host starts with www.)
RewriteCond %{HTTP_HOST} ^www.(.*)$ [NC]
RewriteRule ^ https://%1%{REQUEST_URI} [L,R=301]
`
In this example:
* The first rule ensures all traffic is HTTPS.
The second rule assumes* the traffic is already HTTPS (or will be immediately redirected by the first rule) and then handles the www part. https://%1%{REQUEST_URI} ensures it stays on HTTPS while removing www..
c. Missing [L] (Last) Flag
Always ensure redirect rules that should terminate processing have the [L] flag. Without it, subsequent rules might apply to the already rewritten URI, leading to unintended behavior or loops.
Example:
`apacheconf
# Original rule potentially causing a loop if another rule re-processes the URI
RewriteRule ^old-page.html$ /new-page.html [R=301]
Corrected with [L] flag
RewriteRule ^old-page.html$ /new-page.html [L,R=301]
`
d. Incorrect RewriteBase
If your .htaccess file is in a subdirectory (e.g., /var/www/html/blog/.htaccess), ensure RewriteBase is set correctly.
Example:
For /var/www/html/blog/.htaccess:
`apacheconf
RewriteEngine On
RewriteBase /blog/ # Important for correct path calculation
RewriteRule ^index.php$ – [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /blog/index.php [L]
`
If your .htaccess is in the document root (e.g., /var/www/html/.htaccess), RewriteBase / is usually appropriate, or it can often be omitted if rules are relative to the root.
e. Systematically Test Rules
If you have many rules, comment them out one by one (or in small groups) and test after each change to isolate the problematic rule(s).
`apacheconf
# RewriteEngine On
# RewriteCond %{HTTPS} off
# RewriteRule ^ https://%{HTTPHOST}%{REQUESTURI} [L,R=301]
This rule is currently active and being tested
RewriteEngine On
RewriteCond %{HTTP_HOST} ^www.(.*)$ [NC]
RewriteRule ^ https://%1%{REQUEST_URI} [L,R=301]
`
After testing, uncomment other rules gradually.
5. Debug mod_rewrite with LogLevel (Advanced)
For deep debugging, you can temporarily increase Apache's LogLevel for mod_rewrite. This will produce extremely verbose output in your error log, showing how each rule is processed.
- Open your main Apache configuration file (
/etc/apache2/apache2.conf) or your virtual host file (/etc/apache2/sites-available/your-domain.conf). - Locate the
LogLeveldirective (usuallyLogLevel warn) and add or modify it to includerewrite:traceN, whereNis a number from 1 to 6 (6 being the most verbose). For Apache 2.4,trace1throughtrace8are available.trace5ortrace6is usually sufficient for detailed rewrite debugging.
LogLevel warn rewrite:trace5
[!WARNING] > Enabling
rewrite:tracegenerates an enormous amount of log data very quickly and can severely impact server performance and disk space. Do not use this on a production server for extended periods. Revert theLogLevelimmediately after debugging.
- Restart Apache:
-
`bash - sudo systemctl restart apache2
-
` - Access the problematic URL.
- Monitor the error log (
sudo tail -f /var/log/apache2/error.log). You will see detailed information about howmod_rewriteprocesses each condition and rule. Look for patterns where a URI is repeatedly matched and rewritten by the same or a cyclical set of rules.
Example log snippet you might see during a loop:
`
[rewrite:trace2] [client 127.0.0.1:40830] rewrite 'index.php' -> 'index.php'
[rewrite:trace2] [client 127.0.0.1:40830] add path info and hook 'index.php' to URI
[rewrite:trace2] [client 127.0.0.1:40830] strip per-dir prefix: /var/www/html/index.php -> index.php
[rewrite:trace2] [client 127.0.0.1:40830] applying pattern '^$' to uri 'index.php'
[rewrite:trace2] [client 127.0.0.1:40830] rewrite 'index.php' -> 'index.php'
[rewrite:trace2] [client 127.0.0.1:40830] add path info and hook 'index.php' to URI
… repeated entries indicating a loop …
`
- Once debugging is complete, revert the
LogLevelto its original value (e.g.,LogLevel warn) and restart Apache.
6. Restore Configuration and Test
After implementing your corrected .htaccess rules:
- Ensure
LogLevelis reset inapache2.confor your virtual host file. - Rename your
.htaccess_bak(if you used it for editing) back to.htaccess: -
`bash - mv .htaccess_bak .htaccess
-
` - Restart Apache:
-
`bash - sudo systemctl restart apache2
-
` - Clear your browser cache and test your website thoroughly, including different URLs and sections.
By meticulously following these steps, you should be able to pinpoint and resolve the Apache .htaccess redirect loop causing your 500 Internal Server Error on Ubuntu 20.04 LTS.
Leave a Reply