I have a website that is currently migrating from an old CMS to a new one (Laravel). Only some languages of the website have been moved to Laravel. So basicaly, my root folder of the vhost has something like this:

[D] staticContentDirectory1
[D] staticContentDirectory2
[D] oldCmsDirectory1
[D] oldCmsDirectory2
[D] oldCmsDirectoryN
[D] public
[F] index-of-the-old-cms-still-in-use.php 

I have a .htaccess file that redirects everything that is not handled by the old CMS or in a static content directory to Laravel and looks something like this :

RewriteRule ^((?!staticContentDirectory1|staticContentDirectory2|public)(.*))$ public/$1 [L]

There is also a .htaccess in public, from Laravel, to handle its stuff.

So far, everything is working perfectly fine and we were pretty happy with the result (given the initial constraints).

We discovered an issue though. Somehow, Google managed to discover that /public exists and crawled it, which lead to the URL with a /public/ inside, which since then had created /public/public/ URLs (which, fortunately, returns a 500 error and avoided an infinity of URLs).

Before fixing the CMS and robots.txt we tried to fix that by rewriting/redirecting traffic from /public to /.

A lot of things were tried, a lot of failures happened, almost all in an infinite loop of redirects.

Because every Apache module seems to do its stuff on his own, mixing RedirectMatch and RewriteRule failed. RewriteCond and RewriteRule were either an infinite redirect or totally ignored.

TL;DR: I'm asking for help now : how can one have a RewriteRule to a /folder and yet redirect all traffic from that folder to the root.

1 Answer 1


In order to avoid a redirect loop you need to differentiate between a direct request for the /public subdirectory and a request that has been internally rewritten to the /public subdirectory (which is what your directive above does).

There are several ways to do this. One way is to check the THE_REQUEST server variable, which contains the initial request header (eg. GET /public/whatever HTTP/1.1) as sent from the client and does not change when the URL is rewritten.

Try the following... as a general rule, external redirects should go before internal rewrites, so the following rule should go near the top of the .htaccess file in the /public subdirectory (not the .htaccess file in the document root):

RewriteCond %{THE_REQUEST} ^[A-Z]{3,9}\ /public
RewriteRule (.*) /$1 [R=302,L]

For any direct request that start /public/<whatever> then redirect to /<whatever>. The preceding RewriteCond directive ensures that only direct requests are processed.

The RewriteRule directive might look a bit odd at first (as if it perhaps redirects to itself), but this is because the RewriteRule pattern matches the URL-path less the directory-prefix of where the .htaccess file is located. So, given a request for /public/<whatever>, the $1 backreference only captures <whatever>. So redirects back to /<whatever>.

Note that this is currently a temporary (302) redirect. Change this to a 301 only when you are sure it's working OK. 301s are cached hard by the browser, so can make testing problematic if there is an error.

mixing RedirectMatch and RewriteRule failed

Since directives belong to different modules (mod_alias vs mod_rewrite), they execute at different times during the request, regardless of their apparent order in the config file (mod_rewrite usually runs first). So mixing these directives can indeed result in conflicts. However, RedirectMatch does not give you the control that's required in this instance.

  • 1
    It works perfectly ! Jul 17, 2017 at 8:29

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .