118

There seem to be a decent number of mod_rewrite threads floating around lately with a bit of confusion over how certain aspects of it work. As a result I've compiled a few notes on common functionality, and perhaps a few annoying nuances.

What other features / common issues have you run across using mod_rewrite?

Ben Brocka
  • 2,006
  • 4
  • 34
  • 53
Owen
  • 82,995
  • 21
  • 120
  • 115
  • 5
    See also http://serverfault.com/questions/214512/everything-you-ever-wanted-to-know-about-mod-rewrite-rules-but-were-afraid-to-ask – Michael Myers May 12 '11 at 22:15
  • See also `mod_rewrite tutorial for beginners` https://helponnet.com/2021/04/15/htaccess-tutorial-for-beginers/ – Amit Verma Feb 03 '22 at 18:33

8 Answers8

202

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

  1. 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.

  2. 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]
    
  3. Forcing SSL

    RewriteEngine on
    RewriteCond %{HTTPS} off
    RewriteRule ^(.*)$ https://example.com/$1 [R,L]
    
  4. 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

  1. Mixing mod_alias style redirects with mod_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 with mod_rewrite, but it involves more work than just handling basic redirects as above.

  2. 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
    
  3. [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.

Simon East
  • 55,742
  • 17
  • 139
  • 133
Owen
  • 82,995
  • 21
  • 120
  • 115
  • 10
    Dude, totally the best article on the internet now on mod rewrite. I hate that thing. Im a lighttpd heretic *because* of how much i hate mod_rewrite. – Kent Fredric Nov 13 '08 at 01:20
  • 3
    This has been THE most useful guide I've found on mod_rewrite so far. Just finding out about RewriteLog helped fix so many problems that what was taking me days to track down turned into a few minutes. (I mean the rules were written but I couldn't figure out why they weren't working) – Joe Chin Feb 27 '09 at 10:18
  • 1 year old post, but one of the more useful things I've found on SO - for me. – Erik Jan 23 '10 at 18:07
  • If you only have access to `.htaccess` files then you should largely ignore this post as the `.htaccess`-specific advice is often incorrect. e.g. rewrite *substitution* strings should also *always* omit the leading slash unless you know what you are doing and understand how absolute path redirection works. – TerryE Jan 24 '12 at 15:55
  • 3
    The `[L]` flag means a rule is **last** in current processing, this will not stop rewriting, because they are internal redirects, so your `dirB` apply to `dirC` in next htaccess processing. Alone `RewriteRule ^(.*)$ index.php?query=$1` will be an infinite loop of internal redirects (in practice it's terminated after 10 iterates). **-1** because you're suggest that _[L] is not last_. It's not terminating rewriting process, but **it's last**. – kbec May 15 '12 at 18:19
  • 3
    I believe `RewriteCond %{HTTPS} off` is the preferred way to check for an HTTPS connection (in your example of forcing non-ssl traffic to HTTPS) – Madbreaks Feb 28 '13 at 20:01
  • Wow, that %{ENV:REDIRECT_STATUS} check is a total lifesaver. You just saved what was left of my hair. – mbklein Apr 02 '13 at 01:02
  • What does last do. There seem to be no one that can properly explain. Where does it move to after processing L. Does it restart from the beginning. Does it go to the top. s tthe last only if there is a file name in the request. What exactly is going on. – Lpc_dark May 07 '13 at 15:14
  • It would be nice if this answer also covered the case with MultiViews and mod_rewrite. I'm trying to figure out this one right now, and the answer didn't help me, unfortunately. – Sergei Tachenov Jan 27 '14 at 17:20
  • It's worth noting that the RewriteLog and RewriteLogLevel directives are valid for Apache v2.2. In 2.4 this has changed now to using LogLevel with some module specific levels, see http://httpd.apache.org/docs/current/mod/mod_rewrite.html#logging. Also, the [END] flag referenced above is only available for 2.3.9+ versions of apache. – JaredC Nov 14 '14 at 12:05
  • As @kbec mentioned, the [L] flag ***is*** last, you just have to understand what will trigger the URL parser to resubmit the request for another pass through the rule set. I've added an answer to this question that covers in more detail how the [L] flag works. – JaredC Nov 14 '14 at 12:27
22

if you need to 'block' internal redirects / rewrites from happening in the .htaccess, take a look at the

RewriteCond %{ENV:REDIRECT_STATUS} ^$

condition, as discussed here.

Owen
  • 82,995
  • 21
  • 120
  • 115
mromaine
  • 141
  • 4
  • 7
  • Thanks for me as well, life saver! – BenMorel Aug 05 '13 at 22:49
  • This is indeed a life saver! People should be more aware of that. In fact, I am going to suggest this to every question about `.*` with `[L]` flag I read before I got here. – Qwerty May 24 '14 at 23:56
  • I have seen several modifications to this `200`, `!=200`, `^.`, `^$`. Apparentely the variable gets set to `200` for a redirect, but also other pages (error and stuff) set it to some value. Now that means that you either check if it `is empty`, `is not empty`, `is 200` or `is not 200`, depending on what you need. – Qwerty May 25 '14 at 00:17
18

The deal with RewriteBase:

You almost always need to set RewriteBase. If you don't, apache guesses that your base is the physical disk path to your directory. So start with this:

RewriteBase /
Sean McMillan
  • 10,058
  • 6
  • 55
  • 65
  • Ah. That totally just fixed the problem I was having. Thanks for that! – Tom Savage Feb 17 '10 at 21:15
  • 3
    Any way of saying `RewriteBase .`, or something to indicate that it should keep the URL the same, just changing what you've specified? – Jay K Sep 09 '11 at 19:21
  • Thank you, this is was a priceless piece of info. :) – AturSams Oct 15 '11 at 18:48
  • 2
    You only need to set `RewriteBase` if you are using relative path substitution in the `RewriteRule` directive. It is better to avoid using relative paths. – MrWhite Jan 27 '15 at 16:26
  • 2
    I disagree with this answer. In our dev team we avoid `RewriteBase` altogether as nearly all developers misunderstand what it does. As @w3d said, you only need it if you want to save characters and want to apply the same base to all your RewriteRules in one file. Your code will likely be clearer to others if you avoid it. – Simon East Dec 10 '15 at 02:47
  • 1
    You *never* need `RewriteBase`, as long as you use absolute substitutions. Read the fine manual at [`RewriteBase`](https://httpd.apache.org/docs/current/mod/mod_rewrite.html#rewritebase) – Olaf Dietsche Jan 07 '17 at 23:21
13

Other Pitfalls:

1- Sometimes it's a good idea to disable MultiViews

Options -MultiViews

I'm not well verse on all of MultiViews capabilities, but I know that it messes up my mod_rewrite rules when active, because one of its properties is to try and 'guess' an extension to a file that it thinks I'm looking for.

I'll explain: Suppose you have 2 php files in your web dir, file1.php and file2.php and you add these conditions and rule to your .htaccess :

RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ file1.php/$1 

You assume that all urls that do not match a file or a directory will be grabbed by file1.php. Surprise! This rule is not being honored for the url http://myhost/file2/somepath. Instead you're taken inside file2.php.

What's going on is that MultiViews automagically guessed that the url that you actually wanted was http://myhost/file2.php/somepath and gladly took you there.

Now, you have no clue what just happened and you're at that point questioning everything that you thought you knew about mod_rewrite. You then start playing around with rules to try to make sense of the logic behind this new situation, but the more you're testing the less sense it makes.

Ok, In short if you want mod_rewrite to work in a way that approximates logic, turning off MultiViews is a step in the right direction.

2- enable FollowSymlinks

Options +FollowSymLinks 

That one, I don't really know the details of, but I've seen it mentioned many times, so just do it.

Michael Ekoka
  • 19,050
  • 12
  • 78
  • 79
  • Thanks :) I noticed unexpected surprises like /log/activity turning into /log.txt/activity .. Thanks for the tip :) .. too bad computers never make fun unexpected things happen like accidentally seducing all your female co-workers on facebook :) – AturSams Oct 16 '11 at 12:14
  • 1
    `+FollowSymLinks` is mentioned in the documentation as being mandatory for `mod_rewrite` to work at all, for vague security reasons. – Joey Nov 13 '11 at 12:59
  • Two statements here worry me, immensely: 'I'm not well verse on all of MultiViews capabilities, but I know that it messes up my mod_rewrite rules when active' and this one 'That one, I don't really know the details of, but I've seen it mentioned many times, so just do it.' I wish people like you wouldn't write answers on SO about things you are unsure about. – TheCarver Aug 31 '13 at 12:00
  • 1
    @PaparazzoKid: I think you're mistaking SO for an encyclopedia. It's a community of people coming together to stitch up an understanding of the tech they're working with. Unlike A.W. White and Joey before you, your comment is nearly void of value. MV and FSL are 2 of many Apache's options. My answer is about pitfalls when working with mod_rw specifically, a separate module, which conflicts with some options and works with others. I explained how MV affects mod_rw and mentioned that +FSL is a popular recommendation. Joey confirmed that it's in fact mandatory. What do you bring to the table? – Michael Ekoka Oct 19 '13 at 14:47
  • Thanks. I just spent the best part of an hour getting a legacy site working and trying to debug the rewrite rules, only to find that MultiViews was overriding it all. – Andrew McCombe May 02 '14 at 14:54
  • Just to add to the `FollowSymLinks` note... yes, as Joey states, it is required in order to enable "rewrites in a per-directory context" ([Reference in docs](http://httpd.apache.org/docs/2.4/mod/mod_rewrite.html#what_is_matched)). And `Options +FollowSymLinks` (note the `+`) is a safe way to do this, if it isn't already enabled, providing `AllowOverride` is not blocking this in the server config (which is one way to block mod_rewrite in .htaccess files). – MrWhite Jan 27 '15 at 17:30
5

Equation can be done with following example:

RewriteCond %{REQUEST_URI} ^/(server0|server1).*$ [NC]
# %1 is the string that was found above
# %1<>%{HTTP_COOKIE} concatenates first macht with mod_rewrite variable -> "test0<>foo=bar;"
#RewriteCond search for a (.*) in the second part -> \1 is a reference to (.*)
# <> is used as an string separator/indicator, can be replaced by any other character
RewriteCond %1<>%{HTTP_COOKIE} !^(.*)<>.*stickysession=\1.*$ [NC]
RewriteRule ^(.*)$ https://notmatch.domain.com/ [R=301,L]

Dynamic Load Balancing:

If you use the mod_proxy to balance your system, it's possible to add a dynamic range of worker server.

RewriteCond %{HTTP_COOKIE} ^.*stickysession=route\.server([0-9]{1,2}).*$ [NC]
RewriteRule (.*) https://worker%1.internal.com/$1 [P,L]
DrDol
  • 2,220
  • 2
  • 19
  • 23
4

A better understanding of the [L] flag is in order. The [L] flag is last, you just have to understand what will cause your request to be routed through the URL parsing engine again. From the docs (http://httpd.apache.org/docs/2.2/rewrite/flags.html#flag_l) (emphasis mine):

The [L] flag causes mod_rewrite to stop processing the rule set. In most contexts, this means that if the rule matches, no further rules will be processed. This corresponds to the last command in Perl, or the break command in C. Use this flag to indicate that the current rule should be applied immediately without considering further rules.

If you are using RewriteRule in either .htaccess files or in <Directory> sections, it is important to have some understanding of how the rules are processed. The simplified form of this is that once the rules have been processed, the rewritten request is handed back to the URL parsing engine to do what it may with it. It is possible that as the rewritten request is handled, the .htaccess file or <Directory> section may be encountered again, and thus the ruleset may be run again from the start. Most commonly this will happen if one of the rules causes a redirect - either internal or external - causing the request process to start over.

So the [L] flag does stop processing any further rewrite rules for that pass through the rule set. However, if your rule marked with [L] modified the request, and you're in the .htaccess context or the <Directory> section, then your modifed 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 what happened, it looks like your first rewrite rule with the [L] flag had no effect.

The best way around this is to use the [END] flag (http://httpd.apache.org/docs/current/rewrite/flags.html#flag_end) instead of the [L] flag, if you truly want to stop all further processing of rules (and subsequent reparsing). 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. In this case, you must rely on RewriteCond statements to prevent matching of rules on subsequent passes of the URL parsing engine. 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.

MrWhite
  • 43,179
  • 8
  • 60
  • 84
JaredC
  • 1,360
  • 14
  • 11
3

Another great feature are rewrite-map-expansions. They're especially useful if you have a massive amout of hosts / rewrites to handle:

They are like a key-value-replacement:

RewriteMap examplemap txt:/path/to/file/map.txt

Then you can use a mapping in your rules like:

RewriteRule ^/ex/(.*) ${examplemap:$1}

More information on this topic can be found here:

http://httpd.apache.org/docs/2.0/mod/mod_rewrite.html#mapfunc

B.E.
  • 5,080
  • 4
  • 35
  • 43
  • Ignore this feature if you are using `.htaccess`-based rewrites. It doesn't work in this context. – TerryE Jan 24 '12 at 15:57
  • 2
    The RewriteMap directive must be used in server context (httpd.conf), but once defined there, you may use the map via the RewriteRule in an .htaccess file. – JaredC Nov 14 '14 at 11:58
2

mod_rewrite can modify aspects of request handling without altering the URL, e.g. setting environment variables, setting cookies, etc. This is incredibly useful.

Conditionally set an environment variable:

RewriteCond %{HTTP_COOKIE} myCookie=(a|b) [NC]
RewriteRule .* - [E=MY_ENV_VAR:%b]

Return a 503 response: RewriteRule's [R] flag can take a non-3xx value and return a non-redirecting response, e.g. for managed downtime/maintenance:

RewriteRule .* - [R=503,L]

will return a 503 response (not a redirect per se).

Also, mod_rewrite can act like a super-powered interface to mod_proxy, so you can do this instead of writing ProxyPass directives:

RewriteRule ^/(.*)$ balancer://cluster%{REQUEST_URI} [P,QSA,L]

Opinion: Using RewriteRules and RewriteConds to route requests to different applications or load balancers based on virtually any conceivable aspect of the request is just immensely powerful. Controlling requests on their way to the backend, and being able to modify the responses on their way back out, makes mod_rewrite the ideal place to centralize all routing-related config.

Take the time to learn it, it's well worth it! :)

MrWhite
  • 43,179
  • 8
  • 60
  • 84
cweekly
  • 8,706
  • 1
  • 22
  • 17