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 <Virtual Host>
):
# 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 # ignore existing files RewriteCond %{REQUEST_FILENAME} !-f # ignore existing directories RewriteCond %{REQUEST_FILENAME} !-d # map requests to index.php and append as a query string RewriteRule ^(.*)$ index.php?query=$1
Since Apache 2.2.16 you can also use
FallbackResource
. -
Handling 301/302 redirects:
RewriteEngine on # 302 Temporary Redirect (302 is the default, but can be specified for clarity) RewriteRule ^oldpage\.html$ /newpage.html [R=302] # 301 Permanent Redirect RewriteRule ^oldpage2\.html$ /newpage.html [R=301]
Note: external redirects are implicitly 302 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=302]
-
Forcing SSL
RewriteEngine on RewriteCond %{HTTPS} off RewriteRule ^(.*)$ https://example.com/$1 [R,L]
-
Common flags:
[R]
or[redirect]
– force a redirect (defaults to a 302 temporary redirect)[R=301]
or[redirect=301]
– force a 301 permanent redirect[L]
or[last]
– stop rewriting process (see note below in common pitfalls)[NC]
or[nocase]
– specify that matching should be case insensitive
Using the long-form of flags is often more readable and will help others who come to read your code later.
You can separate multiple flags with a comma:
RewriteRule ^olddir(.*)$ /newdir$1 [L,NC]
Common pitfalls
-
Mixing
mod_alias
style redirects withmod_rewrite
# Bad Redirect 302 /somepage.html http://example.com/otherpage.html RewriteEngine on RewriteRule ^(.*)$ index.php?query=$1 # Good (use mod_rewrite for both) RewriteEngine on # 302 redirect and stop processing RewriteRule ^somepage.html$ /otherpage.html [R=302,L] RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d # handle other redirects RewriteRule ^(.*)$ index.php?query=$1
Note: you can mix
mod_alias
withmod_rewrite
, but it involves more work than just handling basic redirects as above. -
Context affects syntax
Within
.htaccess
files, a leading slash is not used in the RewriteRule pattern:# given: GET /directory/file.html # .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 # Putting a "?" after the slash will allow it to work in both contexts: RewriteRule ^/?directory(.*)$ /newdirectory$1
-
[L] is not last! (sometimes)
The
[L]
flag stops processing any further rewrite rules for that pass through the rule set. However, if the URL was modified in that pass and you’re in the.htaccess
context or the<Directory>
section, then your modified request is going to be passed back through the URL parsing engine again. And on the next pass, it may match a different rule this time. If you don’t understand this, it often looks like your[L]
flag had no effect.# processing does not stop here RewriteRule ^dirA$ /dirB [L] # /dirC will be the final result RewriteRule ^dirB$ /dirC
Our rewrite log shows that the rules are run twice and the URL is updated twice:
rewrite 'dirA' -> '/dirB' internal redirect with /dirB [INTERNAL REDIRECT] rewrite 'dirB' -> '/dirC'
The best way around this is to use the
[END]
flag (see Apache docs) instead of the[L]
flag, if you truly want to stop all further processing of rules (and subsequent passes). However, the[END]
flag is only available for Apache v2.3.9+, so if you have v2.2 or lower, you’re stuck with just the[L]
flag.For earlier versions, you must rely on
RewriteCond
statements to prevent matching of rules on subsequent passes of the URL parsing engine.# Only process the following RewriteRule if on the first pass RewriteCond %{ENV:REDIRECT_STATUS} ^$ RewriteRule ...
Or you must ensure that your RewriteRule’s are in a context (i.e.
httpd.conf
) that will not cause your request to be re-parsed.