Why is this [L] flag failing to prevent application of the next rewrite rule?

Tag: apache , mod-rewrite Author: xyq458834 Date: 2013-05-02

I thought the "L" flag was supposed to prevent subsequent rules from being applied. Yet in this example:

RewriteRule foo bar [L]
RewriteRule bar qux

I get http://mysite/foo rewritten as http://mysite/qux. I expected http://mysite/bar. What am I missing here?

Best Answer

The behavior of the [L] flag is quite poorly described in the Apache docs. In order to understand how it works we first need to know how Apache handles RewriteRules. Let's take a simple example

RewriteRule ^something /somethingelse #1
RewriteRule ^somewhere /somewhereelse #2 
RewriteRule ^someplace /anotherplace  #3

In this situation with multiple rules and no [L] flags, if we were to request /something, Apache will rewrite that /somethingelse (as per #1), then try rules #2 and #3. After all rules are processed it checks if the URL that came out of the RewriteRules is the same as the URL that went in. If it's not, Apache starts processing all the rules again, until input===output (or the maximum number of redirects is met, to prevent infinite loops).

Now, if we change rule #1 and add [L] to it, and we request /something again, Apache will rewrite it to /somethingelse (as per #1), and then stop processing the rules, i.e., it will not process #2 and #3. But then, since the URL that came out is not the same as the URL that went in (that's the crux here), the processing restarts, and rules #2 and #3 will be processed anyway (and #1 also, but doesn't do anything anymore).

In your example if you want to prevent /bar from being redirected to /qux when it was rewritten by the first RewriteRule, you can use

RewriteRule foo bar [L]
RewriteCond %{THE_REQUEST} ^GET\ /bar
RewriteRule bar qux

That will rewrite /bar to /qux only if the user specifically requested /bar, and not if the URL was rewritten from /foo to /bar first.

The trick here is that %{THE_REQUEST} contains the exact HTTP header that was used for the request, and doesn't change when you rewrite the URL, so using that variable you can always check what the original request was for (in contrast to %{REQUEST_URI}, which does change on every rewrite).

comments:

Unfortunately that breaks HEAD and POST requests. Better to rewrite the transformations to avoid the problem.
You can replace RewriteCond %{THE_REQUEST} ^GET\ /bar with RewriteCond %{THE_REQUEST} ^(GET|POST|HEAD)\ /bar if you want to support HEAD and POST requests as well.
Or you can just reverse the order of the rules
@Old Pro No that doesn't work. Please read my post again.

Other Answer1

From the mod_rewrite introduction:

Be sure to configure mod_rewrite's log level to one of the trace levels using the LogLevel directive. It is indispensable in debugging problems with mod_rewrite configuration, since it will tell you exactly how each rule is processed.

To turn on mod_rewrite tracing in Apache 2.4 you can use

LogLevel info mod_rewrite:trace3

However, in Apache 2.2 (which is the current favorite for production) you have to use

RewriteLog "/var/log/apache2/rewrite.log"
RewriteLogLevel 3

Your config

RewriteRule foo bar [L]
RewriteRule bar qux

is problematic because it must be run only once to work the way you want. The L flag in 2.2 doesn't do what you think when the rule is used in a Directory or .htaccess context.

If you are using RewriteRule in either .htaccess files or in 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 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.

However, moving those rules to a service config or virtual host context does work. (Verified on my local server.)

In general you want to write rules so it doesn't matter how many times they are processed. If you absolutely cannot do that, I still don't like using RewriteCond %{THE_REQUEST} ^GET\ /bar to avoid looping or even RewriteCond %{THE_REQUEST} ^[^ ]+ /bar but I don't have a have suggestion at the moment for Apache 2.2.

Apache 2.4 has the END flag for just this situation.