Common causes
- A front-controller rule like RewriteRule ^(.*)$ index.php/$1 without RewriteCond checks, so index.php itself is rewritten again
- Wrong RewriteBase after moving a site into or out of a subfolder
- Rewriting to a file that does not exist, which triggers the rule again
- Rules in both a parent and a subfolder .htaccess that rewrite to each other
- Laravel or other apps installed with the project root as document root, so public/ rewrites chain
- ErrorDocument pointing to a URL that itself gets rewritten in a loop
How to fix it
- Find the rule that loops. On a VPS set LogLevel alert rewrite:trace3 temporarily and read the error log to see each rewrite pass. On shared hosting, comment out rules one by one.
- Exclude real files and folders. Add RewriteCond %{REQUEST_FILENAME} !-f and RewriteCond %{REQUEST_FILENAME} !-d before the front-controller rule so existing files, including index.php, stop the chain.
- Stop rewriting the target. Add an early rule such as RewriteRule ^index\.php$ - [L], or use the [END] flag on the final rule so it does not run again.
- Correct RewriteBase. If the site is in a subfolder, set RewriteBase /subfolder/ and make the target relative (index.php), or the rewritten path will not exist.
- Point the document root to public/. For Laravel and Symfony, set the domain's document root to public/ instead of chaining root and public .htaccess files.
- Do not just raise LimitInternalRecursion. Raising the limit only delays the 500. Fix the rule so the loop ends.
.htaccess (front controller without a loop)
RewriteEngine On
RewriteBase /
RewriteRule ^index\.php$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^ index.php [END] How to stop it happening again
- Always guard catch-all rules with !-f and !-d conditions
- Update RewriteBase whenever you move an app between folders
- Test rewrite changes on staging with trace logging enabled