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

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

  • 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, 2022 at 9: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, 2022 at 9: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, 2022 at 9: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, 2022 at 10:34
  • 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, 2022 at 12:53

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.