All I wanted was to get a better 404 page for the bililite.com site and to use mod_rewrite to standardize my pages. Of course, nothing is ever simple and mod_rewrite is voodoo. So it took a week of experimenting, pulling my hair and staring at a lot of 500 errors, but I think I got things where I wanted them (still don't have a very elegant 404 page, though!). To make sure I didn't have to go through this again, and to help anyone else out there, here are the things I wish I had known:
askApache is my new best friend
There are lots of mod_rewrite tutorials out there on the web, but once you get past the simple stuff, nothing is obvious. askApache digs deeper and explains more of the hack-y stuff that you need to know in order to actually use your .htaccess
file, especially his Crazy Advanced Mod_Rewrite Tutorial, Highly recommended.
Know the difference between Redirect
and Rewrite
Both of these involve changing a URL to a different one. Redirecting, however, tells the browser that it changed (generally by sending a 301 status code) while rewriting (or "internally redirecting") keeps it a secret. Thus if I internally use URL's like //example.com/index.php?main=foo&part=bar
but publicly I want them to look like //example.com/foo/bar
I'll rewrite:
RewriteRule ^([^/\.]+)/([^/\.]+)$ index.php?main=$1&part=$2 [QSA,L]
But to publicly change a URL, say from //example.com/oldname.html
to //example.com/newname.html
I'll redirect:
Redirect Permanent /oldname.html /newname.html
# or
RewriteRule ^oldname.html$ newname.html [R]
Note that it's usually easier to use Redirect
or RedirectMatch
rather than RewriteRule
with the [R] flag. Note also that the Redirect
pattern starts with the /
(or whatever the per-directory prefix is) while RewriteRule
patterns strip the /
out. I've been burned by this! (this is true for .htaccess
only; the per-server configuration file httpd.conf
uses the entire URL. But I'm on a cheap shared server; I can't touch httpd.conf
).
Use Redirect
to append a slash to directories
Generally, if you use a URL like //example.com/foo
you really want //example.com/foo/
. Apache does this automatically for real directories. But if you're doing the /foo/bar
to /index.php?main=foo&part=bar
and /foo
to /index.php?main=foo
, you really want /foo
to be redirected to /foo/
, then /foo/ rewritten to /index.php?main=foo
. If you don't, then relative URL's won't work. If the file returned by /foo
contains a link <a href="bar">
you want the browser to interpret that href as /foo/bar
. But it won't. It thinks the file has the URL /foo
, so its directory is /
and the relative URL bar refers to /bar
. Redirecting /foo
to /foo/
lets the browser know that foo
is to be treated as a directory, so bar
refers to /foo/bar
. Example:
RedirectMatch Permanent ^/([^/\.]+)$ /$1/
RewriteRule ^([^/\.]+)/$ index.php?main=$1 [QSA,L]
RewriteRule ^([^/\.]+)/([^/\.]+)$ index.php?main=$1&part=$2 [QSA,L]
ErrorDocument
should rewrite, not redirect
It's incredibly annoying to type in //example.com/long/complicated/urk.html
(note the typo!) and have the address bar turn into //example.com/404.html. I want the address bar to stay the same so I can edit what I wrote! The ErrorDocument directive uses redirection if a complete URL is specified. This is bad. Just start the URL with a slash and it will use internal redirection (what I'm calling rewriting) and the browser's address bar will remain untouched.
ErrorDocument 404 /404.php
#NOT ErrorDocument 404 http://example.com/404.php
1&1 gets it wrong
I use the cheapest shared-server plan from 1&1 and I've been pretty happy with it: no frills, straight classic LAMP stack, reliable enough for me. But they "hijack" the ErrorDocument
for active pages (.php, .cgi, .pl and I assume others) to their own advertising-laden page. That I can understand (again, it's a cheap plan) but they redirect so you can't correct the URL. So I need to do an end run around them by catching nonexistent files first:
RewriteCond %{REQUEST_FILENAME} \.php$
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule . /404.php [L]
RewriteCond -f
is case sensitive
This burned me badly before I figured it out. The -f
test for RewriteCond
uses the underlying operating system to find the file, which means that on a Linux system, it's case-sensitive. No way around it; the [NC]
flag doesn't help. So when I create gif's on my Windows machine in Paint and save it, it automatically names the file whatever.GIF
and remains that way when I copy it to the Linux server. Now Apache will do fine finding the file if I request it with <img src="whatever.gif"/>
, but if my .htaccess
has a line RewriteCond %{REQUEST_FILENAME} !-f
it will return true since whatever.gif
doesn't exist; whatever.GIF
does.
Hope this saves someone some time (and hair).
Leave a Reply