Score:1

Apache mod_rewrite RewriteRule loops despite L flag

ru flag

Apache/2.4.54.
I am trying to achieve that similar URLs (e.g. "/anystuff.htm") are externally redirected to "/something" which internally is "something.html". But the following rules cause a loop instead of stopping, i.e. "something" is matched

RewriteEngine On
RewriteBase /
RewriteRule ^something$ something.html [L]
RewriteRule ^(some|thing|any|stuff).*/?$ /something [L, R=301,NC]

But it seems the "L" directive does not have an effect, because the redirect to "/something" is matched again by the second rule, causing the loop.

Testing with https://htaccess.madewithlove.com/ suggests it should work as expected. I don't think I can enable logging :-(

Score:1
kz flag

The L flag only stops the current pass through the rewrite engine, it does not stop all processing. The directives that follow are not processed immediately, but (in a directory context) the rewriting process then starts over... During the "second pass" the first rule no longer matches (since the input is now something.html) but the second rule does, so triggers the redirect.

(The rewrite engine effectively "loops" until the URL passes through unchanged. The directives are easier to understand when used in a server (non-directory) context, where the L flag does effectively stop all processing, since there is only a single pass by the rewrite engine.)

However, on Apache 2.4 you can use the END flag instead, to stop all processing by the rewrite engine (in a directory context). For example:

RewriteEngine On

RewriteRule ^something$ something.html [END]
RewriteRule ^(some|thing|any|stuff) /something [R=301,NC,L]

(Note that you had an erroneous space in the flags argument on the second rule. This would have resulted in a parse error, so I assume this was a typo in your question?)

The trailing regex on the second rule (ie. .*/?$) is superfluous.

Using L with a redirect (R flag) is the same as END. All processing stops.

The RewriteBase directive is superfluous in your example.

NB: You should test first with a 302 (temporary) redirect and only change to a 301 (permanent) when you have confirmed that this works as intended. 301s are cached persistently by the browser so can make testing problematic. Consequently, you will need to clear the browser (and any intermediary) caches before testing.

Testing with https://htaccess.madewithlove.com/ suggests it should work as expected.

Unlike a real server, the MWL tester only (effectively) makes a "single pass" through the rules so cannot detect rewrite/redirect loops.


Aside: Ordinarily, you should arrange external redirect directives before internal rewrites. However, the second rule in your example would redirect /something to itself, so your two directives are currently dependent on the order you have them in.

handle avatar
ru flag
Thanks! Unfortunately, something causes the [END] to result in a 404, whereas an [L] delivers the html file. The `RewriteBase /` is required for the server setup. I'll try to use a `RewriteCond` to prevent the loop due to matching "itself"...
kz flag
@handle "whereas an [L] delivers the html file" - I thought you were getting a "redirect loop"? "The `RewriteBase /` is required for the server setup." - what do you mean by that? Do you have other directives in your `.htaccess` file?
handle avatar
ru flag
The END stops the loop but results in 404. To narrow the cause, I have disabled the external redirect rule. The remaining first rule delivers the .html file only if there is no END (none or L) - with END it returns 404. Yes, there are other rules in the .htaccess - I'll try to find a setup where I can test without any other rules to rule out unexpected side effects.
handle avatar
ru flag
The `END` flag appears to cause the rule to ignore the `RewriteBase` so it attempts to load the file from `DOCUMENT_ROOT` - where the file did not exist prior to my experimental "other setup", resulting in the 404.
handle avatar
ru flag
`RewriteCond "%{REQUEST_URI}" "!something"`eliminates the loop.
kz flag
@handle There is no difference between `END` and `L` with regards to relative paths. `END` simply stops all processing and `L` stops the current pass - that is it. It's possible you have other directives that perform additional rewrites in subsequent passes by the rewrite engine? "it attempts to load the file from DOCUMENT_ROOT" - You had `RewriteBase /` - which is the document root. So removing it (in the case of internal rewrites) makes no difference. "where the file did not exist prior to my experimental" - where did it exist??
handle avatar
ru flag
That END stopping "additional subsequent rewrites" causes the issue makes sense. I'm glad the RewriteCond works around that. Thanks again for your help!
kz flag
@handle You're welcome. `"!something"` - This matches any URL that does not _contain_ `something`. Generally you want to be as specific as possible and in this case exclude `/something` only. For this you could use an exact match string comparison (not a regex) with the `=` operator. eg. `!=/something` (or, using a regex it would be `!^/something$`). Surrounding the argument with double quotes is only necessary if it contains _spaces_.
I sit in a Tesla and translated this thread with Ai:

mangohost

Post an answer

Most people don’t grasp that asking a lot of questions unlocks learning and improves interpersonal bonding. In Alison’s studies, for example, though people could accurately recall how many questions had been asked in their conversations, they didn’t intuit the link between questions and liking. Across four studies, in which participants were engaged in conversations themselves or read transcripts of others’ conversations, people tended not to realize that question asking would influence—or had influenced—the level of amity between the conversationalists.