2012-06-21
How I want to do redirects in Apache, especially in .htaccess
files
One of the irritating things about Apache's handling of redirects is
that it makes what I suspect is the most common case the more verbose
and potentially complex to handle, especially if you want to put it in
a .htaccess
file instead of in the main Apache configuration. Since I
just had to go through working this out for our use, I'm going to write
it down in case I need it later.
Suppose that you have an Apache server and you want to redirect the
local URL /~user/
off to http://someserver/url2/ by putting a
.htaccess
file in the user's web home directory; possibly you are even
redirecting your own home page somewhere. Then what you generally want
in the .htaccess file is:
RedirectMatch permanent /~user/.* http://someserver/url2/
This redirects /~user/ and all URLs under it to the single URL http://someserver/url2/, with a permanent redirection (status code 301) instead of Apache's default of temporary redirection (status code 302).
It's worth talking about what happens with several variants. First:
Redirect permanent /~user/ http://someserver/url2/
This is the same as the following RedirectMatch version, which may make what's going on clearer:
RedirectMatch permanent /~user/(.*) http://someserver/url2/$1
The difference is that this redirects a request for, for example, /~user/bar/ off to http://someserver/url2/bar instead of the base URL we provided. If you're going to do this you should know that Redirect has the same issue with matching trailing /'s on each side as the ProxyPass directive does.
The more tricky case is what you need to put in the URL-path (the second argument here) in a .htaccess file in a subdirectory. Suppose you write the redirect in your .htaccess as:
Redirect permanent / http://someserver/
What you will get from a request for /~user/ is a redirection to
http://someserver/~user/. Despite this .htaccess
being located
at /~user/ (logically speaking) and only applying to URLs under it,
everything after the /
in the original URL-path has been taken off and
put on the end of the target URL. Even if this actually works and is
what you want, it's confusing and you should write the redirection with
the real URL-path and URL it applies to spelled out explicitly.
(You can create even more confusing versions with RedirectMatch
.)
This is mentioned in the documentation for the Redirect
directive, although with different amounts of clarity and
thoroughness depending on the version of Apache. See,
for example, the Apache 2.4 Redirect documentation. And
yes, this is inconsistent with how RewriteRule
matches the URL-path.
As with rewrite rules, if you want to redirect
just the main URL and not any sub-URLs you need to be explicit about
this with a RedirectMatch
:
RedirectMatch permanent /~user/$ http://someserver/url2/
This redirects only requests for /~user/ itself; a URL like /~user/page.html will either fail with a 404 or wind up showing the real page on your server (if it exists).
(Because the Redirect
directives always use the full URL-path,
all of this applies to to them regardless of whether they're in
.htaccess
files or in the main Apache configuration. All that being
in a .htaccess
file does for Redirect
et al is limit what URL-paths
they can apply to.)
Sidebar: Redirect and partial matches
Unlike rewrite rules, a Redirect
only matches whole components,
so that a Redirect
for /~user/foo will not match a request for
/~user/foobar. This is mentioned in the documentation from Apache 2.2
onwards and probably applied even before then (but I don't have any
Apache 2.0 or earlier servers to test with). This does not apply to
RedirectMatch
, which will match partial components (so if you have a
URL-path of /~user/foo it will match /~user/foobar).
You might wonder what happens to the 'bar
' portion when you have a
partial-component match in RedirectMatch
. Why, I'm glad you asked.
The answer is that it disappears (it is not appended to the target
URL). In fact the behavior of RedirectMatch
is basically that there
is an implicit '.*
' on the end of the URL-path, so my recommended
RedirectMatch can in fact be rewritten as:
RedirectMatch permanent /~user/ http://someserver/url2/
Once again, I recommend being explicit and not using this trick.