Why is “/public” being added during redirects?
Problem: When redirecting to a URL with a trailing slash, the 301 redirect adds ‘public’, which causes an error. How can this be fixed?
Situation:
- Domain: site.ru
- Directory: subdirectory (contains Laravel files)
- In subdirectory/.htaccess:apache
<IfModule mod_rewrite.c> RewriteEngine On RewriteCond %{HTTP_HOST} ^www\.(.+)$ [NC] RewriteRule ^ https://%1%{REQUEST_URI} [L,R=301] RewriteRule ^(.*)$ public/$1 [L] </IfModule> - In subdirectory/public/.htaccess:apache
<IfModule mod_rewrite.c> <IfModule mod_negotiation.c> Options -MultiViews -Indexes </IfModule> RewriteEngine On # Internally rewrite Filament assets to prefixed path RewriteRule ^subdirectory/css/filament/(.*)$ css/filament/$1 [L] RewriteRule ^subdirectory/js/filament/(.*)$ js/filament/$1 [L] # Handle Authorization Header RewriteCond %{HTTP:Authorization} . RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}] # Redirect Trailing Slashes If Not A Folder... RewriteCond %{REQUEST_FILENAME} !-d RewriteCond %{REQUEST_URI} (.+)/$ RewriteRule ^ %1 [L,R=301] # Send Requests To Front Controller... RewriteCond %{REQUEST_FILENAME} !-d RewriteCond %{REQUEST_FILENAME} !-f RewriteRule ^ index.php [L] </IfModule>
Example:
- Correct URL without slash: https://site.ru/subdirectory/categories/category1 (works)
- Problematic URL with slash: https://site.ru/subdirectory/categories/category1/
- Expected redirect: https://site.ru/subdirectory/categories/category1
- Actual redirect: https://site.ru/subdirectory/public/categories/category1 (error)
Questions:
- Why is “/public” being added to the URL when redirecting from a URL with a trailing slash?
- How can this problem be fixed so the redirect correctly removes the slash without adding “/public”?
Problem with Adding “/public” During Slash Redirect
The issue with adding “/public” during slash redirects occurs due to a conflict between two .htaccess files in your Laravel structure. This is a classic recursive URL rewriting problem in Apache.
Table of Contents
Main Problem Cause
A URL rewriting chain occurs that leads to adding “public”:
- First redirect: When requesting
https://site.ru/subdirectory/categories/category1/ - Rewriting in the first .htaccess:
RewriteRule ^(.*)$ public/$1 [L]converts the request to internalhttps://site.ru/subdirectory/public/categories/category1/ - Processing the second .htaccess: The system processes this path through
public/.htaccess - Slash redirect: The
RewriteRule ^ %1 [L,R=301]rule sees the slash and attempts to redirect tohttps://site.ru/subdirectory/public/categories/category1 - Rule reapplication: This new request again passes through the first .htaccess, which adds “public” again
Key problem: The redirect in public/.htaccess creates a new request that is again processed by both .htaccess files, creating a loop and adding “public” to the URL.
How to Fix It
There are several ways to fix this problem:
Option 1: Modify the redirect rule in public/.htaccess
Replace the current slash redirect rule with:
# Redirect Trailing Slashes If Not A Folder...
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_URI} (.+)/$
RewriteCond %{REQUEST_URI} !^public/ [NC]
RewriteRule ^ %1 [L,R=301]
What this does: Adds the condition RewriteCond %{REQUEST_URI} !^public/ [NC] which prevents the redirect from being applied if the URL already contains “public/”.
Option 2: Use the NS (No Subrequest) flag
Add the NS flag to the redirect rule:
# Redirect Trailing Slashes If Not A Folder...
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_URI} (.+)/$
RewriteRule ^ %1 [L,R=301,NS]
What this does: The NS flag prevents the rule from being applied to internal subrequests that are created by the rewriting rule in the parent .htaccess.
Option 3: Remove the slash redirect from public/.htaccess
If the slash redirect isn’t critical, you can simply remove it from public/.htaccess:
# Comment out or remove these lines:
# RewriteCond %{REQUEST_FILENAME} !-d
# RewriteCond %{REQUEST_URI} (.+)/$
# RewriteRule ^ %1 [L,R=301]
Alternative Solutions
Option 4: Modify the rule structure in the root .htaccess
Modify the main .htaccess so it doesn’t rewrite already rewritten requests:
<IfModule mod_rewrite.c>
RewriteEngine On
# Redirect www to non-www
RewriteCond %{HTTP_HOST} ^www\.(.+)$ [NC]
RewriteRule ^ https://%1%{REQUEST_URI} [L,R=301]
# Exclude already rewritten requests
RewriteCond %{REQUEST_URI} !^public/
RewriteRule ^(.*)$ public/$1 [L]
</IfModule>
Option 5: Use the CO (Cookie) flag to prevent loops
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{HTTP_HOST} ^www\.(.+)$ [NC]
RewriteRule ^ https://%1%{REQUEST_URI} [L,R=301]
# Set cookie to prevent loops
RewriteCond %{HTTP_COOKIE} !^.*laravel_skip_redirect.*$
RewriteRule ^(.*)$ public/$1 [L]
</IfModule>
<IfModule mod_rewrite.c>
<IfModule mod_negotiation.c>
Options -MultiViews -Indexes
</IfModule>
RewriteEngine On
# Add cookie for internal requests
RewriteCond %{REQUEST_URI} ^public/
RewriteRule .* - [CO=laravel_skip_redirect:1:.site.ru]
# ... other rules ...
# Redirect Trailing Slashes
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_URI} (.+)/$
RewriteRule ^ %1 [L,R=301]
</IfModule>
Testing and Verification
After making changes:
- Clear your browser cache and PHP cache (OpCache, APC, etc.)
- Check Apache logs to track requests
- Use browser developer tools to analyze redirects
- Test both scenarios:
https://site.ru/subdirectory/categories/category1/(should redirect to without slash)https://site.ru/subdirectory/categories/category1(should work without redirect)
Conclusion
The problem of adding “/public” during slash redirects occurs due to conflicts between rewriting rules in two .htaccess files. I recommend using Option 1 as the simplest and most elegant solution - add an exclusion condition for paths already containing “public/”.
Key points:
- Always check Apache logs when working with mod_rewrite
- Avoid recursive URL rewriting
- Use flags (L, NS, CO) to control rule behavior
- Test all scenarios after making changes
If the problem persists, you might consider alternative configuration options or consulting with an Apache system administrator.