2

The Background

I run a university club website which has multiple domains, let's say meow.co.uk, foo.uni.ac.uk/bar/meow/ as two relevant cases. (When I upload files using an FTP client, it's the latter that I navigate to.) With my htaccess file, I'm trying to:

  1. Redirect from all other domains to meow.co.uk, the preferred domain, e.g. foo.uni.ac.uk/bar/meow/page.html --> meow.co.uk/page.html
  2. Remove a trailing slash from files that aren't directories, otherwise the user will get an internal server error, e.g. meow.co.uk/page/ --> meow.co.uk/page
  3. Clean the URLs:
    1. Display the contents of the html file if the .html extension is left out, e.g. navigating to meow.co.uk/page shows the same page as navigating to meow.co.uk/page.html
    2. Remove the .html extension from the address bar, e.g. meow.co.uk/page.html --> meow.co.uk/page

The .htaccess

ErrorDocument 404 /errors/404.html

#redirect from the other domains to meow.co.uk
RewriteCond %{HTTP_HOST} !^(www\.)?meow\.co\.uk$ [NC]
RewriteRule ^(.*)$ http://meow.co.uk/$1 [R=302]

#remove trailing slash if not a directory
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.+)/$ /$1 [R=302]

#meow.co.uk/page will display the contents of meow.co.uk/page.html
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME}.html -f
RewriteRule ^(.+)$ $1.html [L,QSA]

#302 from meow.co.uk/page.html to meow.co.uk/page
RewriteCond %{THE_REQUEST} ^[A-Z]{3,9}\ /.*\.html\ HTTP/
RewriteRule ^(.*)\.html$ /$1 [R=302,L]

What works

  • foo.uni.ac.uk/bar/meow/page --> meow.co.uk/page
  • meow.co.uk/page/ --> meow.co.uk/page
  • meow.co.uk/page shows page.html and meow.co.uk/page.html --> meow.co.uk/page

What doesn't work

  • Expected: foo.uni.ac.uk/bar/meow/page/ --> meow.co.uk/page
    • Actual: --> foo.uni.ac.uk/http://meow.co.uk/page/
  • Expected: foo.uni.ac.uk/bar/meow/page.html --> meow.co.uk/page
    • Actual: --> foo.uni.ac.uk/http://meow.co.uk/page

Why don't these work?

3

tl;dr You need the L flag on your redirects, ie. [R=302,L].

RewriteRule ^(.*)$ http://meow.co.uk/$1 [R=302]

Bit of an aside, but... the RewriteRule pattern should be ^bar/meow/(.*) (as mentioned in my answer on your other question), otherwise you won't get the working redirects that you've stated.

For external redirects you generally want to use the L (LAST) flag (briefly mentioned in comments on the other question). Since when you redirect you normally want the rewrite processing to stop and redirect immediately.

This would seem to be the problem here, without the L flag, the rewriting continues and matches the next RewriteRule which further rewrites the request, resulting in a malformed redirect.

This is what's happening in the cases that do not work:

Request: http://foo.uni.ac.uk/bar/meow/page/

The first RewriteRule matches against the URL-path (less the directory prefix, which is simply the / - slash)

bar/meow/page/ >>> http://meow.co.uk/page/ (should stop here)

But the second RewriteRule matches the result of the previous rewrite:

http://meow.co.uk/page/ >>> foo.uni.ac.uk/http://meow.co.uk/page/

Request: http://foo.uni.ac.uk/bar/meow/page.html

bar/meow/page.html >>> http://meow.co.uk/page.html (should stop here)

But the last RewriteRule now matches...

http://meow.co.uk/page.html >>> foo.uni.ac.uk/http://meow.co.uk/page

So, both your earlier redirects need the L flag ie. [R=302,L]. Ironically, the only place you don't need the L flag is where you have used it - in your last redirect! But it is good practise to include it anyway.


Note also, that generally, external redirects should always come before internal rewrites. Whilst your rules work ok, they would be slightly more efficient (and more logical) if you had your internal rewrite last. Currently the last RewriteRule will always match, but the RewriteCond directive prevents it being triggered.

If you reverse these rules then the RewriteRule will only match when needed:

#302 from meow.co.uk/page.html to meow.co.uk/page
RewriteCond %{THE_REQUEST} ^[A-Z]{3,9}\ /.*\.html\ HTTP/
RewriteRule ^(.*)\.html$ /$1 [R=302,L]

#meow.co.uk/page will display the contents of meow.co.uk/page.html
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME}.html -f
RewriteRule (.+) $1.html [L]

#redirect from the other domains to meow.co.uk
RewriteCond %{HTTP_HOST} !^(www\.)?meow\.co\.uk$ [NC]
RewriteRule ^(.*)$ http://meow.co.uk/$1 [R=302]

Another slight problem is that your site would seem to still be accessible by both www.meow.co.uk and meow.co.uk. You should probably have a second rule to resolve this and redirect one to the other:

RewriteCond %{HTTP_HOST} =www.meow.co.uk [NC]
RewriteRule (.*) http://meow.co.uk/$1 [R=302,L]

The = prefix on the CondPattern makes it an exact string match, it is no longer a regex (which is why the dots are not escaped).

  • Therein lay the rub: my previous understanding was that using the L flag would prevent the URL from being changed further. But actually the URL will still be changed by later rewrite rules, the L flag means the rewrite will be applied at that stage – binaryfunt Oct 2 '15 at 11:38
  • Well, yes and no... the L causes rewriting to stop and the current rule is applied. In the case of an external redirect (R=3xx) this will result in a "redirect" response being sent to the client (ie. a Location: HTTP response header contains the new destination URL). The client then makes another request to the new destination URL and the process starts all over again. In the case of an internal rewrite (no R flag) control is handed back to the URL rewriting engine and the process starts again from the top (which is why you need to be careful of recursive rewrite loops). – MrWhite Oct 2 '15 at 16:07

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.