Apache mod_alias RedirectMatch everything except specific pattern

borfast picture borfast · Oct 16, 2014 · Viewed 8.3k times · Source

Good old regular expressions are driving me nuts.

I need to redirect all traffic in Apache 2.4 from HTTP to HTTPS, except for "/bt/sub/[a_few_endings]", using Redirect from mod_alias (can't use mod_rewrite).

I tested the following regular expression in all online testers I know (e.g. http://regex101.com/) and all confirm that the regex should indeed match everything except the URLs I don't want it to match:

^/(?!bt/sub/(went_active|success|cancel|expired)).*$ 

As far as I can tell, this should match everything in http://local.mysite.com and redirect it to https://local.mysite.com, except for the following four:

Still, Apache redirects everything, including the above URLs I don't want redirected.

I found several similar questions in SO but most of them are answered in the light of mod_rewrite, which is not what I want/need, and the ones that people say have worked have not worked for me.

Here's my virtual host configuration as it currently stands:

<VirtualHost *:80> 
    ServerName local.mysite.com 
    RedirectMatch 302 ^/(?!bt/sub/(went_active|success|cancel|expired)).*$ https://local.mysite.com 
    DocumentRoot /home/borfast/projects/www/mysite/public 
    #Header set Access-Control-Allow-Origin * 
    SetEnv LARAVEL_ENV localdev 

    <Directory /home/borfast/projects/www/mysite/public/> 
        Options All 
        DirectoryIndex index.php 
        AllowOverride All 
        Require all granted 
    </Directory> 
</VirtualHost>

Please help and prevent me from going crazy :)


UPDATE: There's something weird going on: apparently when the requested URL/path can be found, Apache ignores the expression in RedirectMatch and redirects the client, even though the RedirectMatch tells it not to.

To test this I created a new virtualhost from scratch inside a separate VM freshly installed with Ubuntu Trussty 64, loaded with Apache 2.4. This new virtual host contained just the ServerName, RedirectMatch and DocumentRoot directives, like this:

<VirtualHost *:80>
    ServerName testing.com
    RedirectMatch 302 ^/(?!bt/sub/(went_active|success)$).*$ https://othersite.com/

    DocumentRoot /home/vagrant/www
</VirtualHost>

I created the directory /home/vagrant/www/bt/sub/went_active to make sure Apache could get to at least one of the two possible URLs. When trying to access http://testing.com:8080, I get redirected, as expected.

Then the weirdness comes: when accessing http://testing.com:8080/bt/sub/went_active, the URL that matches the directory I created, I am still redirected, even though I shouldn't be, but when accessing http://testing.com:8080/bt/sub/success, I don't get redirected and instead get a 403 Forbidden.

I may be losing my sanity over this but it seems that when Apache sees that it could serve the request and it matches the regular expression in RedirectMatch that should prevent the redirect, it decides to ignore the regular expression and do the redirect anyway. Three letters for this: WTF?!?!?!

Answer

Cheery picture Cheery · Oct 16, 2014

As it was said in comments - it is easier to do with mod_rewrite. Possible solutions

RewriteEngine On
RewriteCond %{REQUEST_URI} !^/bar/(abcd|baz|barista|yo)$ [NC]
RewriteRule ^ http://site/ [R=301,L]

Another one (for .htaccess, as initial / is removed from RewriteRule)

RewriteEngine On
RewriteRule !^bar/(abcd|baz|barista|yo)$ http://site/ [R=301,L,NC]

And solution by RedirectMatch

RedirectMatch permanent ^(?!/bar/(abcd|baz|barista|yo)$).* http://site/

All work perfectly, the problem that you might have on a testing/debugging state is that browser caches 301 response. So, when you are trying to check or to write the correct code - use 302 response, not 301. And remove NC flag if case insensitivity is not required.