Mod_Rewrite unexpected behavior L flag

You are missing an important fact about the L flag:

It is therefore important, if you are using RewriteRule directives in one of these [.htaccess, <Directory>] contexts, that you take explicit steps to avoid rules looping, and not count solely on the [L] flag to terminate execution of a series of rules

From: L|last (Flag); bold by me

That means, only by using the L does not have your desired effect to prevent the interal redirect. The INTERNAL REDIRECT happens here because it must happen, you have specified it with your .htaccess configuration. The L flag is not the right flag to prevent the INTERNAL REDIRECT.

Let's look closer at your question and what actually happens:

I can't understand it because I've placed the [L] flag just to ensure that the mod_rewrite stops.

Is just that you have the wrong undestanding of the L flag. It will only stop for the current rewriting, meaning, the RewriteRule directives beneath it are not going to be processed in the current round (the inner loop).

If the URI changed L will re-inject into the the next round (the outer loop) as the following technical details flowchart shows:

http://httpd.apache.org/docs/current/rewrite/tech.html

To highlight where the L flag kicks in and where the INTERNAL REDIRECT happens, this is the same graphic with some annotations for your specfic (first) URI rewrite:

enter image description here

It shows that the L flag only exits the inner loop but if the URI has been rewritten (changed) - as in your case - the outer loop takes care that the changed URI will be passed again to all your rewrite rules.

Instead you might want to formulate a condition as the following example from that part of the manual shows:

RewriteBase /
RewriteCond %{REQUEST_URI} !=/index.php
RewriteRule ^(.*) /index.php?req=$1 [L,PT]

(PT has it's own manual entry, is is more or less not part of the solution, just noting because I quoted the example as-is)

What you actually want to use is the END flag:

RewriteRule css css.php [END,NC]

However contact your system administrator if you have the needed apache version for it (Available in 2.3.9 and later). If not, you need to operate with RewriteCond.


hakre's answer illustrates well what is happening. Besides using the END flag you have a few more options:

  • Use %{ENV:REDIRECT_STATUS} to prevent any further rewrites. This will change after an internal redirect. See this page for more information what is actually happening:

    RewriteCond %{ENV:REDIRECT_STATUS} ^$
    RewriteRule css css.php [L,NC]
    
  • Use %{THE_REQUEST} to match against the request that was made to the server. Since this does not change when you internally redirect, this can be used to prevent further redirects

    RewriteCond %{THE_REQUEST} ^(GET|POST)\ /css\ HTTP
    RewriteRule ^ css.php [L,NC]
    
  • Use a dummy variable in the query string. This would allow to prevent redirecting by defining this variable in a link, but we will use it here to prevent multiple passes:

    RewriteCond %{QUERY_STRING} !noredir=1
    RewriteRule css css.php?noredir=1 [L,QSA,NC]
    

Please note that for each of these examples you need to use this construction for each individual rule.