NeuroAgent

Why is /public added during redirect?

Solution for the problem of /public being added during trailing slash redirect in Laravel. Learn the causes and ways to fix .htaccess file conflicts.

Question

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:

Questions:

  1. Why is “/public” being added to the URL when redirecting from a URL with a trailing slash?
  2. How can this problem be fixed so the redirect correctly removes the slash without adding “/public”?
NeuroAgent

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”:

  1. First redirect: When requesting https://site.ru/subdirectory/categories/category1/
  2. Rewriting in the first .htaccess: RewriteRule ^(.*)$ public/$1 [L] converts the request to internal https://site.ru/subdirectory/public/categories/category1/
  3. Processing the second .htaccess: The system processes this path through public/.htaccess
  4. Slash redirect: The RewriteRule ^ %1 [L,R=301] rule sees the slash and attempts to redirect to https://site.ru/subdirectory/public/categories/category1
  5. 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:

apache
# 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:

apache
# 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:

apache
# 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:

apache
<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

apache
<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:

  1. Clear your browser cache and PHP cache (OpCache, APC, etc.)
  2. Check Apache logs to track requests
  3. Use browser developer tools to analyze redirects
  4. 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.