The Mystery of mod_rewrite
I collected a bit of information from mod_rewrite "fun" over the years at stackoverflow, but then figured I'd post it here too, since I've been a bit lax about writing. Don't worry though (because I know you are), I have a few things in the pipes, including the next article in our MVC bare bones framework series, and my take on a drupal based image gallery.
On a completely unrelated note, the macbook (version 2) is finally here! I'm not going to touch it, because I don't want to be blamed for anything that goes wrong this time around. On the plus side, Apple support very generously gave the SO a $30 off coupon, for purchases over $200 for her troubles. (I'm not sure if that last sentence was sarcastic or not...)
And now, mod_rewrite fun:
Where to place mod_rewrite rules
mod_rewrite rules may be placed within the httpd.conf file, or within the .htaccess file. If you have access to httpd.conf, placing rules here will offer a performance benefit (as the rules are processed once, as opposed to each time the .htaccess file is called).
Logging mod_rewrite requests
Logging may be enabled from within the httpd.conf file (including ):
# logs can't be enabled from .htaccess # loglevel > 2 is really spammy! RewriteLog /path/to/rewrite.log RewriteLogLevel 2
Common use cases
- To funnel all requests to a single point:
RewriteEngine on RewriteCond %{REQUEST_FILENAME} !-f # ignore existing files RewriteCond %{REQUEST_FILENAME} !-d # ignore existing directories RewriteRule ^(.*)$ index.php?query=$1 # map requests to index.php and # append as a query string - Handling 301/302 redirects:
RewriteEngine on RewriteRule ^oldpage.html$ /newpage.html [R=302] # 302 Redirect RewriteRule ^oldpage2.html$ /newpage.html [R=301] # 301 Redirect
note: external redirects are implicitly 301 redirects:
# this rule: RewriteRule ^somepage.html$ http://google.com # is equivalent to: RewriteRule ^somepage.html$ http://google.com [R] # and: RewriteRule ^somepage.html$ http://google.com [R=301]
- Forcing SSL
RewriteEngine on RewriteCond %{SERVER_PORT} 80 RewriteRule ^(.*)$ https://mysite.com/$1 [R,L] - Common flag usage:
- [R] force a redirect (default 301)
- [R=302] force a 302 redirect
- [L] stop rewriting process (see note below in common pitfalls)
- [NC] case insensitive matches
You can mix and match flags:
RewriteRule ^olddir(.*)$ /newdir$1 [L,NC]
Common pitfalls
- Mixing mod_alias style redirects with mod_rewrite
# Bad Redirect 302 /somepage.html http://mysite.com/otherpage.html RewriteEngine on RewriteRule ^(.*)$ index.php?query=$1 # Good (use mod_rewrite for both) RewriteEngine on RewriteRule ^somepage.html$ /otherpage.html [R=302] # 302 redirect RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteRule ^(.*)$ index.php?query=$1 # handle other redirects
note: you can mix mod_alias with mod_rewrite, but it involves more work than just handling basic redirects as above.
- Context affects syntax
# given: GET /directory/file.html
Within .htaccess files, a leading slash is not used in the pattern:
# .htaccess # result: /newdirectory/file.html RewriteRule ^directory(.*)$ /newdirectory$1 # .htaccess # result: no match! RewriteRule ^/directory(.*)$ /newdirectory$1 # httpd.conf # result: /newdirectory/file.html RewriteRule ^/directory(.*)$ /newdirectory$1
- [L] is not last! (sometimes)
RewriteRule ^dirA$ /dirB [L] # processing does not stop here RewriteRule ^dirB$ /dirC # /dirC will be the final result
Within the .htaccess context, [L] will not force mod_rewrite to stop. it will continue to trigger internal sub-requests. Our rewrite log reveals the details:
rewrite 'dirA' -> '/dirB' internal redirect with /dirB [INTERNAL REDIRECT] rewrite 'dirB' -> '/dirC'

