1

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 :-(

MrWhite
  • 12,647
  • 4
  • 29
  • 41
handle
  • 113
  • 3

1 Answers1

1

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.

MrWhite
  • 12,647
  • 4
  • 29
  • 41
  • 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"... – handle Nov 18 '22 at 09:38
  • @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? – MrWhite Nov 18 '22 at 09:42
  • 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 Nov 18 '22 at 09:59
  • 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 Nov 18 '22 at 10:34
  • `RewriteCond "%{REQUEST_URI}" "!something"`eliminates the loop. – handle Nov 18 '22 at 10:40
  • @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?? – MrWhite Nov 18 '22 at 11:00
  • That END stopping "additional subsequent rewrites" causes the issue makes sense. I'm glad the RewriteCond works around that. Thanks again for your help! – handle Nov 18 '22 at 12:24
  • 1
    @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_. – MrWhite Nov 18 '22 at 12:53