From: Rich Bowen Date: Wed, 27 May 2026 03:46:07 +0000 (+0000) Subject: Backport rewrite guide improvements from trunk X-Git-Tag: 2.4.68-rc1-candidate~64 X-Git-Url: http://git.ipfire.org/gitweb/index.cgi?a=commitdiff_plain;h=c554bfec5805e1a6f5320b0f11f41230781143d2;p=thirdparty%2Fapache%2Fhttpd.git Backport rewrite guide improvements from trunk Sync reorganized and expanded mod_rewrite documentation from trunk: - Rewritten index with structured navigation - Expanded flags reference with new examples (chain, cookie, DPI) - Renamed htaccess doc to 'Per-directory Rewrites' with new sections on path stripping, RewriteBase, and loop behavior - Expanded tech.xml with detailed per-directory processing explanation - New recipes in remapping.xml: HTTPS behind load balancer, ACME exemption, trailing-slash handling, front-controller patterns - Added prg: map Python example in rewritemap.xml - Cross-reference links and glossary terms throughout - Fixed incorrect tags: SameSite is 2.4.47, UnsafePrefixStat is 2.4.60, UNC is 2.4.63 (were erroneously marked 2.5.1) - Removed N flag iteration-limit paragraph (trunk-only feature) git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/branches/2.4.x@1934664 13f79535-47bb-0310-9956-ffa450edef68 --- diff --git a/docs/manual/rewrite/avoid.xml b/docs/manual/rewrite/avoid.xml index 5975f41ee7..e85eed1c34 100644 --- a/docs/manual/rewrite/avoid.xml +++ b/docs/manual/rewrite/avoid.xml @@ -55,10 +55,11 @@ files to work with, you may need to resort to Module documentation mod_rewrite introduction Redirection and remapping +Per-directory Rewrites +RewriteRule Flags Virtual hosts Using RewriteMap - +Technical details
Simple Redirection @@ -97,38 +98,30 @@ Redirect "/one/" "http://one.example.com/" Canonical Hostnames recipe.

-

To redirect http URLs to https, do the -following:

- - -<VirtualHost *:80> - ServerName www.example.com - Redirect "/" "https://www.example.com/" -</VirtualHost> - -<VirtualHost *:443> - ServerName www.example.com - # ... SSL configuration goes here -</VirtualHost> - - -

The use of RewriteRule to perform this task may be -appropriate if there are other RewriteRule directives in -the same scope. This is because, when there are Redirect -and RewriteRule directives in the same scope, the -RewriteRule directives will run first, regardless of the -order of appearance in the configuration file.

- -

In the case of the http-to-https redirection, the use of -RewriteRule would be appropriate if you don't have access -to the main server configuration file, and are obliged to perform this -task in a .htaccess file instead.

+

To redirect http URLs to https, a +Redirect in a dedicated +HTTP virtual host is the cleanest approach. See the +Forcing HTTPS recipe for +the recommended configuration and the +mod_rewrite alternative for .htaccess +use.

+ + Processing order +

If you do mix Redirect + and RewriteRule in the + same context, be aware that their execution order depends on where + they appear. In server/virtual-host context, + mod_rewrite runs first; in per-directory context + (.htaccess), mod_alias runs first. See + Module Processing Order for + details.

+
URL Aliasing

The Alias directive -provides mapping from a URI to a directory - usually a directory outside +provides mapping from a URL-path to a directory - usually a directory outside of your DocumentRoot. Although it is possible to perform this mapping with mod_rewrite, Alias is the preferred method, for diff --git a/docs/manual/rewrite/flags.xml b/docs/manual/rewrite/flags.xml index c485ef634b..84b3b81839 100644 --- a/docs/manual/rewrite/flags.xml +++ b/docs/manual/rewrite/flags.xml @@ -34,9 +34,11 @@ providing detailed explanations and examples.

Module documentation mod_rewrite introduction Redirection and remapping +Per-directory Rewrites Virtual hosts Using RewriteMap When not to use mod_rewrite +Technical details
Introduction

A RewriteRule can have @@ -121,8 +123,12 @@ RewriteRule "^search/(.*)$" "/search.php?term=$1" "[B= ?]"

To limit the characters escaped this way, see #flag_bne and #flag_bctls

+ +

See URL Encoding and Decoding for +a full explanation of how Apache decodes URIs before pattern matching.

+
BNP|backrefnoplus (don't escape space to +)

The [BNP] flag instructs RewriteRule to escape the space character @@ -137,8 +143,12 @@ RewriteRule "^search/(.*)$" "/search.php/$1" "[B,BNP]"

This flag is available in version 2.4.26 and later.

+ +

See URL Encoding and Decoding for +background on how encoding is handled in the rewrite pipeline.

+
BCTLS

The [BCTLS] flag is similar to the [B] flag, but only escapes control characters and the space character. This is the same set of @@ -176,6 +186,17 @@ control moves on to the next rule. However, if it does not match, then the next rule, and any other rules that are chained together, are skipped.

+ +# Rewrite legacy product URLs to the new catalog app, +# and add a tracking parameter — but only for the rewritten ones. +RewriteRule "^/products/([0-9]+)$" "/catalog/item/$1" [C] +RewriteRule "^/catalog/(.*)$" "/catalog/$1?via=legacy" [QSA] + + +

Without the [C] flag, the second rule would also match requests +that arrive at /catalog/ directly. The chain ensures the +second rule is only applied when the first rule matched.

+
CO|cookie @@ -242,7 +263,7 @@ browsers that support this feature.
samesite
If set to anything other than false or 0, the SameSite attribute is set to the specified value. Typical values are None, -Lax, and Strict. Available in 2.5.1 and later.
+Lax, and Strict. Available in 2.4.47 and later. @@ -263,23 +284,27 @@ minutes (24 hours) and is returned for all URIs.

DPI|discardpath -

The DPI flag causes the PATH_INFO that was appended to the rewritten -URI to be discarded.

-

In per-directory context, the URI that each RewriteRule -compares against is the concatenation of the current values of the URI -and PATH_INFO.

- -

The current URI can be the initial URI as requested by the client, the -result of a previous round of mod_rewrite processing, or the result of -a prior rule in the current round of mod_rewrite processing.

- -

In contrast, the PATH_INFO that is appended to the URI before each -rule reflects only the value of PATH_INFO before this round of -mod_rewrite processing. As a consequence, if large portions -of the URI are matched and copied into a substitution in multiple +

The DPI flag causes the PATH_INFO +that was appended to the rewritten +URL-path to be discarded.

+ +

In per-directory context, the +URL-path each +RewriteRule compares against is the concatenation +of the current URL-path and PATH_INFO.

+ +

The current URL-path can be the initial path as requested by the +client, the result of a previous round of mod_rewrite +processing, or the result of a prior rule in the current round of +mod_rewrite processing.

+ +

In contrast, the PATH_INFO that is appended to the URL-path before +each rule reflects only the value of PATH_INFO before this round of +mod_rewrite processing. As a consequence, if large +portions of the URL-path are matched and copied into a substitution in multiple RewriteRule directives, without regard for -which parts of the URI came from the current PATH_INFO, the final -URI may have multiple copies of PATH_INFO appended to it.

+which parts of the URL-path came from the current PATH_INFO, the final +URL-path may have multiple copies of PATH_INFO appended to it.

Use this flag on any substitution where the PATH_INFO that resulted from the previous mapping of this request to the filesystem is not of @@ -289,6 +314,18 @@ not be recalculated until the current round of mod_rewrite proc completes. Subsequent rules during this round of processing will see only the direct result of substitutions, without any PATH_INFO appended.

+ + +# Request: /app/script.php/extra/path (PATH_INFO is /extra/path) +# Without DPI, the substitution would see "script.php/extra/path" +# and could inadvertently copy PATH_INFO into the result. +RewriteRule "^script\.php(.*)$" "/new-app/handler$1" [DPI] + + +

The [DPI] flag discards /extra/path so that only the +substitution result (/new-app/handler) is passed to +subsequent rules or the final request.

+
E|env @@ -331,7 +368,7 @@ contexts, including CGI programs, other RewriteRule directives, or CustomLog directives.

The following example sets an environment variable called 'image' to a -value of '1' if the requested URI is an image file. Then, that +value of '1' if the requested URL-path is an image file. Then, that environment variable is used to exclude those requests from the access log.

@@ -366,10 +403,34 @@ RewriteCond "%{ENV:rewritten}" =1

Note that environment variables do not survive an external redirect. You might consider using the [CO] flag to set a -cookie. For per-directory rewrites, where the final -substitution is processed as an internal redirect, environment -variables from the previous round of rewriting are prefixed with -"REDIRECT_".

+cookie.

+ + REDIRECT_ prefix after internal redirects +

In per-directory context, + a successful substitution triggers an internal redirect. When this + happens, all environment variables set during the previous pass — + including those created with [E=VAR:VAL] — are renamed + with a REDIRECT_ prefix. A variable you set as + rewritten becomes REDIRECT_rewritten in + the redirected request.

+ +

To test for the renamed variable, reference it with the + prefix:

+
+ + +RewriteRule "^/horses/(.*)" "/ponies/$1" [E=rewritten:1] + +# In the next pass, the variable has been renamed: +RewriteCond "%{ENV:REDIRECT_rewritten}" =1 +RewriteRule "^/ponies/(.*)" "-" [E=seen_redirect:1,L] + + +

If the request is redirected multiple times, the prefix stacks: + REDIRECT_REDIRECT_rewritten, and so on. See + REDIRECT_ variables for + the complete description of this mechanism.

+
END @@ -396,7 +457,7 @@ RewriteRule "^(.*)$" "/index.php" [END]

This does not apply to new requests resulting from external redirects.

-

See the .htaccess looping +

See the Per-directory Rewrites discussion for a detailed explanation of why [L] behaves differently in per-directory context, and when [END] is the right choice.

@@ -416,8 +477,8 @@ RewriteRule "\.exe" "-" [F]

This example uses the "-" syntax for the rewrite target, which means -that the requested URI is not modified. There's no reason to rewrite to -another URI, if you're going to forbid the request.

+that the requested URL-path is not modified. There's no reason to rewrite to +another URL-path, if you're going to forbid the request.

When using [F], an [L] is implied - that is, the response is returned immediately, and no further rules are evaluated.

@@ -479,32 +540,12 @@ further rules will be processed. This corresponds to the C. Use this flag to indicate that the current rule should be applied immediately without considering further rules.

-

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

- -

It is therefore important, if you are using RewriteRule directives in one of these -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, as shown below.

- -

An alternative flag, [END], can be used to terminate not only the -current round of rewrite processing but prevent any subsequent -rewrite processing from occurring in per-directory (htaccess) -context. This does not apply to new requests resulting from external -redirects.

+

In per-directory context, +[L] stops the current pass through the ruleset, but the rewritten +request may be re-processed from the top — which can cause loops. +Use the [END] flag to prevent this, or see +the Per-directory Rewrites document +for a full discussion of the issue and alternative solutions.

The example given here will rewrite any request to index.php, giving the original request as a query string @@ -536,19 +577,13 @@ so until there are no more As to be replaced. RewriteRule "(.*)A(.*)" "$1B$2" [N]

You can think of this as a while loop: While this -pattern still matches (i.e., while the URI still contains an +pattern still matches (i.e., while the URL-path still contains an A), perform this substitution (i.e., replace the A with a B).

-

In 2.5.0 and later, this module returns an error after 10,000 iterations to -protect against unintended looping. An alternative maximum number of -iterations can be specified by adding to the N flag.

- -# Be willing to replace 1 character in each pass of the loop -RewriteRule "(.+)[><;]$" "$1" [N=32000] -# ... or, give up if after 10 loops -RewriteRule "(.+)[><;]$" "$1" [N=10] - +

Use with extreme caution. If no termination condition is met, this +flag will cause the rule to loop indefinitely. In most cases you should +use [L] instead of [N], unless you truly intend iterative processing.

@@ -556,7 +591,7 @@ RewriteRule "(.+)[><;]$" "$1" [N=10]

Use of the [NC] flag causes the RewriteRule to be matched in a case-insensitive manner. That is, it doesn't care whether letters appear -as upper-case or lower-case in the matched URI.

+as upper-case or lower-case in the matched URL-path.

In the example below, any request for an image file will be proxied to your dedicated image server. The match is case-insensitive, so that @@ -601,6 +636,9 @@ being converted to its hexcode equivalent, %23, which will then result in a 404 Not Found error condition.

+

See URL Encoding and Decoding for +the full picture of how Apache encodes and decodes URIs during +rewriting.

NS|nosubreq @@ -625,6 +663,15 @@ Images, javascript files, or css files, loaded as part of an HTML page, are not subrequests - the browser requests them as separate HTTP requests.

+ + +# Only rewrite direct requests to the front controller, +# not subrequests from SSI includes or mod_dir. +RewriteCond "%{REQUEST_FILENAME}" !-f +RewriteCond "%{REQUEST_FILENAME}" !-d +RewriteRule "^(.*)$" "/app/index.php?page=$1" [NS,L] + +
P|proxy @@ -642,7 +689,7 @@ pushed through the proxy, and any following rules will not be considered.

-You must make sure that the substitution string is a valid URI +You must make sure that the substitution string is a valid URL (typically starting with http://hostname) which can be handled by the mod_proxy. If not, you will get an error from the proxy module. Use this flag to achieve a @@ -653,10 +700,14 @@ to map remote content into the namespace of the local server.

Security Warning

Take care when constructing the target URL of the rule, considering -the security impact from allowing the client influence over the set of -URLs to which your server will act as a proxy. Ensure that the scheme -and hostname part of the URL is either fixed, or does not allow the -client undue influence.

+the security impact of allowing the client influence over the set of +URLs to which your server will act as a proxy. If any part of the +target URL is derived from user input (backreferences, query strings, +etc.), an attacker may be able to cause your server to make requests +to arbitrary internal or external hosts. This is known as a +Server-Side Request Forgery (SSRF) vulnerability. Ensure that the +scheme and hostname part of the URL is either fixed, or does not allow +the client undue influence.

@@ -677,16 +728,6 @@ will be used automatically.

Note: mod_proxy must be enabled in order to use this flag.

-Security warning -

Take care when constructing the target URL of the rule, considering -the security impact of allowing the client influence over the set of -URLs to which your server will act as a proxy. If any part of the -target URL is derived from user input (backreferences, query strings, -etc.), an attacker may be able to cause your server to make requests -to arbitrary internal or external hosts. This is known as a -Server-Side Request Forgery (SSRF) vulnerability.

-
-
PT|passthrough @@ -694,7 +735,7 @@ Server-Side Request Forgery (SSRF) vulnerability.

The target (or substitution string) in a RewriteRule is assumed to be a file path, by default. The use of the [PT] flag causes it to be treated -as a URI instead. That is to say, the +as a URL-path instead. That is to say, the use of the [PT] flag causes the result of the RewriteRule to be passed back through URL mapping, so that location-based mappings, such as -.

QSA|qsappend

-When the replacement URI contains a query string, the default behavior +When the replacement URL contains a query string, the default behavior of RewriteRule is to discard the existing query string, and replace it with the newly generated one. Using the [QSA] flag causes the query strings to be combined. @@ -759,10 +800,10 @@ will be discarded.

QSD|qsdiscard

-When the requested URI contains a query string, and the target URI does +When the requested URL contains a query string, and the target URL does not, the default behavior of RewriteRule is to copy that query -string to the target URI. Using the [QSD] flag causes the query string +string to the target URL. Using the [QSD] flag causes the query string to be discarded.

@@ -773,12 +814,18 @@ Using [QSD] and [QSA] together will result in [QSD] taking precedence.

-If the target URI has a query string, the default behavior will be +If the target URL has a query string, the default behavior will be observed - that is, the original query string will be discarded and replaced with the query string in the RewriteRule target -URI. +URL.

+ +# Redirect old search URLs to the new path, discarding the query string. +# /search?q=term&page=2 becomes /find (query string removed) +RewriteRule "^/search" "/find" [QSD,R=301,L] + +
QSL|qslast @@ -838,7 +885,7 @@ status using their symbolic names: temp (default),

You will almost always want to use [R] in conjunction with [L] (that is, use [R,L]) because on its own, the [R] flag prepends -http://thishost[:thisport] to the URI, but then passes this +http://thishost[:thisport] to the URL-path, but then passes this on to the next rule in the ruleset, which can often result in 'Invalid URI in request' warnings.

@@ -847,6 +894,24 @@ URI in request' warnings. spec. Using an unrecognized status code will result in a 500 error and error log message.

+[R=4xx] does not serve the substitution +

When a status code outside the 300-399 range is specified (e.g., +[R=403] or [R=410]), the +substitution string is ignored. The URL you wrote as the +target is not served to the client. Instead, httpd returns the +specified status code and handles it through the normal error +response path (including any configured +ErrorDocument). +If you want to deny access, the [F] and +[G] flags are clearer ways to express the +same intent.

+
+ + +# Redirect requests for the old docs path to the new location. +RewriteRule "^/docs/(.*)$" "http://docs.example.com/$1" [R=301,L] + +
S|skip @@ -857,7 +922,7 @@ RewriteRule and any preceding RewriteCond directives match). This can be thought of as a goto statement in your rewrite ruleset. In the following example, we only want to run the -RewriteRule if the requested URI doesn't correspond with an +RewriteRule if the requested URL-path doesn't correspond with an actual file.

@@ -947,6 +1012,26 @@ The L flag can be useful in this context to end the rewritten result has a '?' in the substitution. This protects from a malicious URL taking advantage of a capture and re-substitution of the encoded question mark.

+ + +# A PHP front controller that routes all requests via a query parameter. +# Without UnsafeAllow3F, a request like /page%3Fname=test would return +# 403 Forbidden because the rewritten substitution contains '?' while +# the original request contains an encoded '%3F'. +RewriteCond "%{REQUEST_FILENAME}" !-f +RewriteCond "%{REQUEST_FILENAME}" !-d +RewriteRule "(.+)" "index.php?route=$1" [L,QSA,UnsafeAllow3F] + + + +This flag exists because of CVE-2024-38474. +Use it only on rules where you are certain that user-supplied %3F +in the request cannot be exploited to manipulate the query string of the +substitution target. Prefer restructuring URLs to avoid encoded question +marks where possible. + +
UnsafePrefixStat

Setting this flag is required in server-scoped substitutions @@ -955,14 +1040,48 @@ The L flag can be useful in this context to end the This protects from a malicious URL causing the expanded substitution to map to an unexpected filesystem location.

-

2.5.1

+

2.4.60

+ + +# This rule starts the substitution with a backreference. +# Since 2.4.60, this is rejected by default to prevent the expanded +# path from escaping the document root (CVE-2024-38475). +# Only add UnsafePrefixStat after verifying the substitution cannot +# resolve to a filesystem path outside your web root. +RewriteRule "^/mirror/(.+)$" "$1" [PT,UnsafePrefixStat] + + + +This flag exists because of CVE-2024-38475. +Without it, a substitution beginning with a backreference or variable +that happens to match an existing filesystem path could allow requests +to escape the document root. Use this flag only after confirming that +the substitution is adequately constrained. + +
UNC

Setting this flag prevents the merging of multiple leading slashes, as used in Windows UNC paths. The flag is not necessary when the rules substitution starts with multiple literal slashes.

-

2.5.1

+

2.4.63

+ + +# On Windows, rewrite to a UNC file share using a variable. +# Without [UNC], the leading slashes in the substitution would be +# collapsed (//server/share becomes /server/share). +RewriteCond "%{HTTP_HOST}" "^(.+)\.internal$" +RewriteRule "^/shared/(.*)$" "//%1/fileshare/$1" [UNC] + + +

This flag is only relevant on Windows. It prevents Apache from +merging the leading double slash (//) that denotes a +UNC path when the path is constructed from a backreference or +variable. If the substitution begins with literal double slashes, +no flag is needed.

+
diff --git a/docs/manual/rewrite/htaccess.xml b/docs/manual/rewrite/htaccess.xml index a11ff66739..5db5037db3 100644 --- a/docs/manual/rewrite/htaccess.xml +++ b/docs/manual/rewrite/htaccess.xml @@ -23,7 +23,7 @@ Rewrite -mod_rewrite and .htaccess files +Per-directory Rewrites @@ -123,6 +123,36 @@ where the rule lives:

in either the pattern or the substitution. This is the single most common source of confusion with per-directory rewriting.

+

When a substitution is made in per-directory context, Apache issues +a new internal subrequest with the rewritten URL, restarting request +processing from the top. If the substitution is a relative path, +the RewriteBase directive +determines what URL-path prefix is prepended. This subrequest +mechanism is also why rules can loop — see +below.

+ + +Do not start .htaccess patterns with / +

Because the directory prefix (including the trailing +slash) is stripped before matching, patterns in +per-directory context +will never match a leading slash. A pattern +beginning with ^/ will never match in this +context. A rule like RewriteRule "^/foo" ... will +silently fail to match anything when placed in a +.htaccess file.

+
+ +

If you need to match against the full original +URL-path (including the directory prefix), use +%{REQUEST_URI} in a +RewriteCond:

+ + +RewriteCond "%{REQUEST_URI}" "^/admin/" +RewriteRule "^.*$" "-" [F] + +
When you need RewriteBase @@ -162,7 +192,7 @@ RewriteRule "^(.*)$" "index.php" [L] For this particular use case - routing all unmatched requests to a front controller - the -FallbackResource directive +FallbackResource directive is a simpler and more efficient alternative to mod_rewrite.

Without the RewriteBase "/myapp/" line, the rewritten @@ -271,6 +301,41 @@ would be prohibitively expensive to repeat each time.

+
Which contexts support rewrite rules? + +

Rewrite rules are supported in +per-directory context +(.htaccess files, +Directory, and +If blocks).

+ +

Although rewrite rules are syntactically permitted in +Location +and Files +sections (including their regex counterparts), this is +unsupported and should never be necessary. Relative +substitutions, in particular, are likely to break in +these contexts.

+ +These containers silently change rewrite +behavior +

Placing a RewriteRule +inside a Directory, +If, or +Location block +— even inside a +VirtualHost in the +main server config — silently switches to +per-directory context behavior. +This means the leading slash is stripped from the URL before pattern +matching, substitutions trigger an internal redirect (with loop risk), +and the [L] flag no longer +truly stops processing — use +[END] instead.

+
+ +
+
Rule inheritance with RewriteOptions

By default, mod_rewrite rules are not @@ -340,38 +405,27 @@ performance.

-
Which contexts support rewrite rules? - -

Rewrite rules are supported in -per-directory context -(.htaccess files, -Directory, and -If blocks).

- -

Although rewrite rules are syntactically permitted in -Location -and Files -sections (including their regex counterparts), this is -unsupported and should never be necessary. Relative -substitutions, in particular, are likely to break in -these contexts.

- -These containers silently change rewrite -behavior -

Placing a RewriteRule -inside a Directory, -If, or -Location block -— even inside a -VirtualHost in the -main server config — silently switches to -per-directory context behavior. -This means the leading slash is stripped from the URL before pattern -matching, substitutions trigger an internal redirect (with loop risk), -and the [L] flag no longer -truly stops processing — use -[END] instead.

-
+
Browser caching of 301 redirects + +

When you issue a 301 Moved Permanently redirect +(via [R=301] or Redirect permanent), the browser is allowed to cache +that response indefinitely. This means that even after you fix an +incorrect redirect rule in your configuration, returning visitors +may continue to be sent to the old (wrong) destination without +ever contacting your server again.

+ +

While debugging redirect rules, use [R=302] +(temporary redirect) instead of [R=301]. Switch to 301 +only after you have confirmed the rule is correct. If you have +already issued an incorrect 301, affected users will need to clear +their browser cache (or use a private/incognito window) to see +the corrected behavior.

+ +Search engines also cache 301 redirects. An incorrect 301 +may take days or weeks to be re-crawled, even after the +configuration is fixed. This is another reason to test with 302 +first.
diff --git a/docs/manual/rewrite/index.xml b/docs/manual/rewrite/index.xml index e33fdc25f7..9b7dd148bd 100644 --- a/docs/manual/rewrite/index.xml +++ b/docs/manual/rewrite/index.xml @@ -27,58 +27,65 @@ +

The great thing about mod_rewrite is it gives you all the +configurability and flexibility of Sendmail. The downside to +mod_rewrite is that it gives you all the configurability and +flexibility of Sendmail. +-- Brian Behlendorf

+ +

Despite the tons of examples and docs, mod_rewrite is voodoo. +Damned cool voodoo, but still voodoo. +-- Brian Moore

+

mod_rewrite provides a way to modify incoming URL requests, dynamically, based on regular expression rules. This allows you to map arbitrary URLs onto your internal URL structure in any way you like.

-

It supports an unlimited number of rules and an - unlimited number of attached rule conditions for each rule to - provide a really flexible and powerful URL manipulation - mechanism. The URL manipulations can depend on various tests: - server variables, environment variables, HTTP - headers, time stamps, external database lookups, and various other - external programs or handlers, can be used to achieve granular URL - matching.

- -

Rewrite rules can operate on the full URLs, including the path-info - and query string portions, and may be used in per-server context - (httpd.conf), per-virtualhost context (VirtualHost blocks), or - per-directory context - (.htaccess files and Directory blocks). The - rewritten result can lead to further rules, internal - sub-processing, external request redirection, or proxy - passthrough, depending on what flags you - attach to the rules.

- -

Since mod_rewrite is so powerful, it can indeed be rather - complex. This guide supplements the reference documentation, and - attempts to allay some of that complexity, and provide highly - annotated examples of common scenarios that you may handle with - mod_rewrite. But we also attempt to show you when you should not - use mod_rewrite, and use other standard Apache features instead, - thus avoiding this unnecessary complexity.

- - - +

This guide supplements the + reference manual with annotated examples, conceptual + explanations, and practical advice. It is organized as follows:

+ +
+
Introduction
+
Core concepts: regular expression syntax, RewriteRule and +RewriteCond basics, and how mod_rewrite fits into the request +processing lifecycle.
+ +
Per-directory Rewrites
+
The key differences between using rewrite rules in server +configuration versus per-directory +context, including path stripping, RewriteBase, and the looping behavior +of the [L] flag.
+ +
RewriteRule Flags
+
A complete reference for all flags that can modify the behavior of +a RewriteRule, with examples for each.
+ +
Using RewriteMap
+
How to use external lookup sources — text files, DBM databases, +SQL queries, and internal functions — to drive your rewrite +rules.
+ +
Redirection and Remapping
+
Recipes for common tasks: HTTPS redirection, canonical hostnames, +trailing slash normalization, front-controller routing, and more.
+ +
Dynamic Virtual Hosts
+
Using mod_rewrite to dynamically map hostnames to document roots +without individual VirtualHost blocks.
+ +
When NOT to use mod_rewrite
+
Many common tasks are better accomplished with simpler directives. +This document shows the alternatives and when to prefer them.
+ +
Technical Details
+
How mod_rewrite hooks into the Apache request processing phases, +and the order in which rules and conditions are evaluated.
+
-mod_rewrite reference -documentation +mod_rewrite Reference Documentation Mapping URLs to the Filesystem mod_rewrite wiki diff --git a/docs/manual/rewrite/intro.xml b/docs/manual/rewrite/intro.xml index 0156c01f9f..e73f84ab25 100644 --- a/docs/manual/rewrite/intro.xml +++ b/docs/manual/rewrite/intro.xml @@ -35,11 +35,13 @@ but this doc should help the beginner get their feet wet. Module documentation - Redirection and remapping +Per-directory Rewrites +RewriteRule Flags Virtual hosts Using RewriteMap When not to use mod_rewrite +Technical details
Introduction

The Apache module mod_rewrite is a very powerful and @@ -70,12 +72,12 @@ it will tell you exactly how each rule is processed.

Regular Expressions -

mod_rewrite uses the Perl Compatible -Regular Expression vocabulary. In this document, we do not attempt +

mod_rewrite uses the Perl Compatible +Regular Expressions vocabulary, via the PCRE2 library. In this document, we do not attempt to provide a detailed reference to regular expressions. For that, we -recommend the PCRE man pages, the +recommend the PCRE2 documentation, the Perl regular -expression man page, and , and Mastering Regular Expressions, by Jeffrey Friedl (the third edition is from 2006, but regular expression syntax is essentially unchanged, and this @@ -230,15 +232,33 @@ of three arguments separated by spaces. The arguments are

The Pattern is a regular expression. -It is initially (for the first rewrite rule or until a substitution occurs) -matched against the URL-path of the incoming request (the part after the -hostname but before any question mark indicating the beginning of a query -string) or, in per-directory context, against the request's path relative -to the directory for which the rule is defined. Once a substitution has -occurred, the rules that follow are matched against the substituted -value. +In server or virtualhost context, it is matched against the +%-decoded URL-path +of the incoming request — the part after the hostname and port, +and not including the query string (e.g., /app/index.html). +In per-directory context, +the pattern is matched against the request's path relative to the +directory for which the rule is defined (with the directory prefix +stripped — see Per-directory +Rewrites for details).

+

Once a substitution has occurred, any rules that follow are +matched against the substituted value.

+ +

The Pattern is matched only against the URL-path +— not the hostname, port, or query string. To match against +those, use a +RewriteCond with the +%{HTTP_HOST}, %{SERVER_PORT}, or +%{QUERY_STRING} variables respectively.

+ +mod_rewrite operates entirely on the URL-path +and HTTP headers. It cannot inspect the request body (e.g., POST +data). If you need to make routing decisions based on request body +content, handle that in your application logic or use a module such +as mod_request paired with a custom filter. +

Syntax of the RewriteRule directive
@@ -384,27 +404,19 @@ href="rewritemap.html">RewriteMap supplementary documentation.

.htaccess files -

Rewriting is typically configured in the main server configuration -setting (outside any Directory section) or -inside VirtualHost -containers. This is the easiest way to do rewriting and is -recommended. It is possible, however, to do rewriting -inside Directory -sections or .htaccess -files at the expense of some additional complexity. This technique -is called per-directory rewrites. -Note that +

It is possible to use rewrite rules in +per-directory context +(.htaccess files and +Directory blocks), +but the rules behave differently there — in particular, the directory +prefix is stripped from the URL before matching. See the +Per-directory Rewrites +document for full details. Note that If and Location blocks also trigger per-directory behavior — see Which contexts support rewrite rules?.

-

The main difference from per-server rewrites is that the path -prefix of the directory containing the .htaccess file is -stripped before matching in -the RewriteRule. In addition, the RewriteBase should be used to assure the request is properly mapped.

-
Security Considerations diff --git a/docs/manual/rewrite/remapping.xml b/docs/manual/rewrite/remapping.xml index 55c3b0e3d9..b1be63c187 100644 --- a/docs/manual/rewrite/remapping.xml +++ b/docs/manual/rewrite/remapping.xml @@ -36,10 +36,12 @@ including detailed descriptions of how each works.

Module documentation mod_rewrite introduction - +Per-directory Rewrites +RewriteRule Flags Virtual hosts Using RewriteMap When not to use mod_rewrite +Technical details
@@ -129,104 +131,136 @@ RewriteRule "^(.*)" "https://%{SERVER_NAME}$1" [R=301,L]

See also the When not to use mod_rewrite document for more discussion of the Redirect approach.

+ + Behind a load balancer or SSL terminator +

The %{HTTPS} variable is not a general-purpose + environment variable — it queries mod_ssl + directly. If SSL/TLS is terminated at an upstream load balancer + or reverse proxy, mod_ssl is not handling the + connection and %{HTTPS} will always report + off, even when the original client connected over + HTTPS.

+ +

In this situation, check the header set by the upstream proxy + instead. Most load balancers set + X-Forwarded-Proto:

+
+ + +RewriteEngine On +RewriteCond "%{HTTP:X-Forwarded-Proto}" =http [NC] +RewriteRule "^(.*)" "https://%{SERVER_NAME}$1" [R=301,L] + + + +

Only trust X-Forwarded-Proto if you control the + upstream proxy and it overwrites the header on every request. An + attacker can forge this header when connecting directly to your + server. Consider restricting access so that only your load + balancer can reach the backend, or use + mod_remoteip to validate the source.

+
+
-
+
- Trailing Slash Normalization + Exempting ACME challenge requests from HTTPS redirect
Description:
-

You want to ensure that URLs for directories always end with - a trailing slash, or conversely, that they never do. This is a - common requirement for SEO and for consistent URL handling by - web applications.

+

You have forced all traffic to HTTPS (as above), but your + ACME client (Let's Encrypt, Certbot, etc.) needs plain HTTP + access to /.well-known/acme-challenge/ to complete + domain validation.

Solution:
-

To add a trailing slash to URLs that map to directories:

+

Place an exception before your HTTPS redirect + rule:

-RewriteCond "%{REQUEST_FILENAME}" -d -RewriteCond "%{REQUEST_URI}" "!/$" -RewriteRule "^(.*)$" "$1/" [R=301,L] +RewriteEngine On +RewriteRule "^/\.well-known/acme-challenge/" - [L] +RewriteCond "%{HTTPS}" !=on +RewriteRule "^(.*)" "https://%{SERVER_NAME}$1" [R=301,L] +
-

To remove a trailing slash (except for actual directories):

+
Discussion:
+ +
+

The dash (-) substitution means "do not rewrite." + Combined with [L], it stops rule processing for any + request matching the ACME challenge path, allowing it to be + served over plain HTTP. All other requests continue to the + next rule and are redirected to HTTPS as usual.

+ +

If you are using the Redirect approach in a dedicated + port-80 VirtualHost, use an + Alias and + RedirectMatch + instead:

-RewriteCond "%{REQUEST_FILENAME}" !-d -RewriteCond "%{REQUEST_URI}" "(.+)/$" -RewriteRule "^" "%1" [R=301,L] - +<VirtualHost *:80> + ServerName www.example.com -
+ # Allow ACME challenges over HTTP + Alias "/.well-known/acme-challenge/" "/var/www/acme/.well-known/acme-challenge/" + <Directory "/var/www/acme/.well-known/acme-challenge"> + Require all granted + </Directory> -
Discussion:
+ # Everything else goes to HTTPS + RedirectMatch permanent "^/(?!\.well-known/acme-challenge/)" "https://www.example.com/$0" +</VirtualHost> + -
-

Apache's mod_dir already handles trailing - slash redirects for real directories when - DirectorySlash is enabled - (the default). You only need a mod_rewrite rule - if you want to enforce trailing slash behavior for URLs that do - not correspond to actual directories on disk, or if you want to - remove trailing slashes.

-
+
- Canonical www/non-www Hostname + Trailing Slash Normalization
Description:
-

You want to force all requests to use either - www.example.com or example.com, - not both. This ensures search engines treat them as one site - and prevents cookie scope issues.

+

You want to ensure that URLs for directories always end with + a trailing slash, or conversely, that they never do. This is a + common requirement for SEO and for consistent URL handling by + web applications.

Solution:
-

The best approach does not use mod_rewrite at - all. Place a Redirect - in the virtual host for the non-canonical hostname:

+

To add a trailing slash to URLs that map to directories:

-# Redirect example.com -> www.example.com -<VirtualHost *:80 *:443> - ServerName example.com - Redirect permanent "/" "https://www.example.com/" -</VirtualHost> +RewriteCond "%{REQUEST_FILENAME}" -d +RewriteCond "%{REQUEST_URI}" "!/$" +RewriteRule "^(.*)$" "$1/" [R=301,L] -

If you only have .htaccess access:

- - -# Add www -RewriteEngine On -RewriteCond "%{HTTP_HOST}" "!^www\." [NC] -RewriteRule "^(.*)" "https://www.%{HTTP_HOST}$1" [R=301,L] - +

To remove a trailing slash (except for actual directories):

-# Remove www -RewriteEngine On -RewriteCond "%{HTTP_HOST}" "^www\.(.+)$" [NC] -RewriteRule "^(.*)" "https://%1$1" [R=301,L] +RewriteCond "%{REQUEST_FILENAME}" !-d +RewriteCond "%{REQUEST_URI}" "(.+)/$" +RewriteRule "^" "%1" [R=301,L]
@@ -234,10 +268,13 @@ RewriteRule "^(.*)" "https://%1$1" [R=301,L]
Discussion:
-

See also the Canonical Hostnames - recipe above, which covers the general case. This recipe focuses - specifically on the www/non-www choice, which is the most common - hostname canonicalization need.

+

Apache's mod_dir already handles trailing + slash redirects for real directories when + DirectorySlash is enabled + (the default). You only need a mod_rewrite rule + if you want to enforce trailing slash behavior for URLs that do + not correspond to actual directories on disk, or if you want to + remove trailing slashes.

@@ -247,52 +284,19 @@ RewriteRule "^(.*)" "https://%1$1" [R=301,L] Front Controller / Application Routing -
-
Description:
- -
-

Most modern web frameworks (PHP, Python, Ruby, etc.) use a - single entry point - often called a "front controller" - that - handles all requests. URLs like /products/widget - are routed to index.php (or equivalent), which - parses the URL internally.

-
- -
Solution:
- -
+

Most modern web frameworks route all requests through a single + entry point (a "front controller"). The + FallbackResource directive + handles this more simply and efficiently than + mod_rewrite. See When NOT to use mod_rewrite + for the recommended approach.

- For this use case, the - FallbackResource directive is - almost always the better choice. See the - Fallback Resource recipe above. - -

If you need mod_rewrite (for example, to add - additional conditions), the standard pattern is:

- - -RewriteEngine On -RewriteCond "%{REQUEST_FILENAME}" !-f -RewriteCond "%{REQUEST_FILENAME}" !-d -RewriteRule "^(.*)$" "/index.php" [L] - - -

The !-f and !-d conditions skip the - rule for requests that map to an existing file or directory, so - static assets (images, CSS, JavaScript) are still served - directly.

-
- -
Discussion:
- -
-

In .htaccess context, consider using - [END] instead of [L] to avoid - reprocessing loops. See the - .htaccess looping discussion - for details.

-
-
+

If you genuinely need mod_rewrite for this (for + example, to add conditions beyond "file doesn't exist"), see the + per-directory rewrites + document for an annotated example that also demonstrates + RewriteBase usage.

@@ -530,6 +534,16 @@ RewriteRule "^/?(.*)" "http://www.example.com/$1" [L,R,NE] example.com, you could use the following recipe:

+

To do the reverse - strip the www. prefix - swap the +condition:

+ + +RewriteCond "%{HTTP_HOST}" "^www\.(.+)$" [NC] +RewriteRule "^(.*)" "http://%1/$1" [L,R,NE] + + +

To generically add www. to any hostname:

+ RewriteCond "%{HTTP_HOST}" "!^www\." [NC] RewriteCond "%{HTTP_HOST}" "!^$" @@ -539,23 +553,25 @@ RewriteRule "^/?(.*)" "http://www.%{HTTP_HOST}/$1" [L,R,NE]

These rulesets will work either in your main server configuration file, or in a .htaccess file placed in the DocumentRoot of the server.

- - +
Discussion:

If you have access to the server configuration, a Redirect in a dedicated VirtualHost - is the cleanest approach. Use the + is the cleanest approach. Canonicalizing the hostname ensures that + search engines treat your site as a single entity and avoids + cookie scope issues that arise when the same site is reachable + under multiple names.

+

Use the If directive as a middle ground, and mod_rewrite only if you are limited to .htaccess.

- - +
@@ -702,65 +718,6 @@ rather than rewriting URLs.

-
-Fallback Resource - -
-
Description:
-
You want a single resource (say, a certain file, like index.php) to -handle all requests that come to a particular directory, except those -that should go to an existing resource such as an image, or a css file.
- -
Solution:
-
-

As of version 2.2.16, you should use the FallbackResource directive for this:

- - -<Directory "/var/www/my_blog"> - FallbackResource index.php -</Directory> - - -

However, in earlier versions of Apache, or if your needs are more -complicated than this, you can use a variation of the following rewrite -set to accomplish the same thing:

- - -<Directory "/var/www/my_blog"> - RewriteBase "/my_blog" - - RewriteCond "/var/www/my_blog/%{REQUEST_FILENAME}" !-f - RewriteCond "/var/www/my_blog/%{REQUEST_FILENAME}" !-d - RewriteRule "^" "index.php" [PT] -</Directory> - - -

If, on the other hand, you wish to pass the requested URI as a query -string argument to index.php, you can replace that RewriteRule with:

- - -RewriteRule "(.*)" "index.php?$1" [PT,QSA] - - -

Note that these rulesets can be used in a .htaccess -file, as well as in a <Directory> block.

- -
- -
Discussion:
- -
-

The FallbackResource directive -is almost always the better choice for this use case. See the -When not to use mod_rewrite -document for a simpler one-line alternative.

-
- -
- -
-
Rewrite query string @@ -808,7 +765,7 @@ RewriteRule "(.*)" "-" [F]
  • This solution shows the reverse of the previous ones, copying - path components (perhaps PATH_INFO) from the URL into the query string. + path components (perhaps PATH_INFO) from the URL into the query string. # The desired URL might be /products/kitchen-sink, and the script expects # /path?products=kitchen-sink. diff --git a/docs/manual/rewrite/rewritemap.xml b/docs/manual/rewrite/rewritemap.xml index 2821e5547c..45f473417a 100644 --- a/docs/manual/rewrite/rewritemap.xml +++ b/docs/manual/rewrite/rewritemap.xml @@ -33,9 +33,11 @@ and provides examples of each of the various Module documentation mod_rewrite introduction Redirection and remapping + RewriteRule Flags Virtual hosts - Per-directory rewrites (.htaccess) + Per-directory Rewrites When not to use mod_rewrite +Technical details
    Introduction @@ -142,7 +144,7 @@ may be used, and give examples of each.

    >RewriteRule:

    -

    Redirect a URI to an all-lowercase version of itself

    +

    Redirect a URL-path to an all-lowercase version of itself

    RewriteMap lc int:tolower RewriteRule "(.*)" "${lc:$1}" [R] @@ -385,7 +387,7 @@ by many requests. Mutex directive.

    A simple example is shown here which will replace all dashes with - underscores in a request URI.

    + underscores in a request URL-path.

    Rewrite configuration

    @@ -402,6 +404,40 @@ for line in sys.stdin: print(line.strip().replace('-', '_'), flush=True) +

    A more complete example shows the typical pattern for a + prg: map program: read a line, look up the result, and write + it back. Diagnostic output is written to STDERR, + which ends up in the Apache error log.

    + +

    Rewrite configuration

    + +RewriteMap vhost2docroot "prg:/www/bin/vhost_lookup.py" +RewriteRule "^/(.*)$" "${vhost2docroot:%{HTTP_HOST}}/$1" + + +

    vhost_lookup.py

    + +#!/usr/bin/env python3 +"""Map a hostname to its document root directory.""" +import sys + +VHOSTS = { + "example.com": "/srv/www/example", + "blog.example.com": "/srv/www/blog", +} + +for line in sys.stdin: + host = line.strip().lower() + docroot = VHOSTS.get(host) + if docroot: + print(docroot, flush=True) + else: + # "NULL" tells mod_rewrite the lookup failed + print("NULL", flush=True) + print(f"vhost_lookup: no match for {host!r}", file=sys.stderr) + + + Caution!
    • Keep your rewrite map program as simple as possible. If the program @@ -411,7 +447,9 @@ requests.
    • Be sure to turn off buffering in your program. In the Python example above, this is done by passing flush=True to print(). Buffered I/O will cause httpd to wait for the -output, and so it will hang.
    • +output, and so it will hang. This is the single most common cause +of prg: maps appearing to "do nothing" — the program has the +answer but httpd never sees it because it is stuck in a buffer.
    • Remember that there is only one copy of the program, started at server startup. All requests will need to go through this one bottleneck. This can cause significant slowdowns if many requests must go through diff --git a/docs/manual/rewrite/tech.xml b/docs/manual/rewrite/tech.xml index e015d4d143..8e1eded638 100644 --- a/docs/manual/rewrite/tech.xml +++ b/docs/manual/rewrite/tech.xml @@ -32,9 +32,10 @@ and URL matching.

      Module documentation mod_rewrite introduction Redirection and remapping +RewriteRule Flags Virtual hosts Using RewriteMap -Per-directory rewrites (.htaccess) +Per-directory Rewrites When not to use mod_rewrite
      API Phases @@ -71,64 +72,131 @@ and URL matching.

      In each of these cases, mod_rewrite rewrites the REQUEST_URI either to a new URL, or to a filename.

      -

      In per-directory context (i.e., within .htaccess files - and Directory blocks), these rules are being applied - after a URL has already been translated to a filename. Because of - this, the URL-path that mod_rewrite initially compares RewriteRule directives against - is the full filesystem path to the translated filename with the current - directories path (including a trailing slash) removed from the front.

      - -

      To illustrate: If rules are in /var/www/foo/.htaccess and a request - for /foo/bar/baz is being processed, an expression like ^bar/baz$ would - match.

      - -

      If a substitution is made in per-directory context, a new internal - subrequest is issued with the new URL, which restarts processing of the - request phases. If the substitution is a relative path, the RewriteBase directive - determines the URL-path prefix prepended to the substitution. - In per-directory context, care must be taken to - create rules which will eventually (in some future "round" of per-directory - rewrite processing) not perform a substitution to avoid looping. - (See RewriteLooping - for further discussion of this problem.)

      - -

      Because of this further manipulation of the URL in per-directory - context, you'll need to take care to craft your rewrite rules - differently in that context. In particular, remember that the - leading directory path will be stripped off of the URL that your - rewrite rules will see. Consider the examples below for further - clarification.

      - - - - - - - - - - - - - - - - - - - - - - - -
      Location of ruleRule
      VirtualHost sectionRewriteRule "^/images/(.+)\.jpg" "/images/$1.gif"
      .htaccess file in document rootRewriteRule "^images/(.+)\.jpg" "images/$1.gif"
      .htaccess file in images directoryRewriteRule "^(.+)\.jpg" "$1.gif"
      - -

      For even more insight into how mod_rewrite manipulates URLs in - different contexts, you should consult the log entries made during - rewriting.

      +

      In per-directory context, + rules are applied during the Fixup phase after the URL has already + been translated to a filename. This changes what the pattern matches + against and how substitutions are handled. See the + Per-directory Rewrites + document for practical details on path stripping, RewriteBase, and + how to avoid looping.

      + +
      + +
      Module Processing Order + +

      mod_rewrite and mod_alias both + operate during the URL-to-filename translation phase, but + mod_rewrite runs first regardless + of the order in which directives appear in the configuration file. + This is determined by the hook priority each module registers, not + by source order.

      + +

      The practical consequence: when both RewriteRule and Redirect (or RedirectMatch) are present in the + same server or virtual-host context, the rewrite rules are + evaluated first. If a RewriteRule matches and rewrites + the URL-path (or returns a redirect), Redirect never sees + the request.

      + + +# In this configuration, the Redirect is never reached for /old +# because the RewriteRule matches first — even though +# the Redirect appears earlier in the file. +Redirect "/old" "http://example.com/new" +RewriteRule "^/old" "/other" [L] + + + Per-directory context reverses the order +

      In per-directory context, + the situation is different. mod_alias directives like + Redirect still run in the URL-to-filename translation + phase, but mod_rewrite rules run later, in the + Fixup phase. This means that in per-directory context, + Redirect is evaluated before + RewriteRule.

      +
      + +

      Because of this inconsistency between contexts, mixing + mod_rewrite and mod_alias + directives in the same scope is a common source of confusion. The + simplest advice: choose one module for a given task. If you need + rewrite conditions or pattern matching, use + RewriteRule exclusively. If a simple prefix redirect + suffices, use Redirect and don't add rewrite rules + that might interact with it.

      + +
      + +
      URL Encoding and Decoding + +

      Apache httpd unescapes URL-encoded characters in the request URL-path before any + RewriteRule pattern + matching takes place. A request for + /my%20page/cats%3Fdogs is decoded to + /my page/cats?dogs, and that decoded string is what the + RewriteRule pattern matches against.

      + +

      This means you cannot write a pattern that matches the literal + URL-encoded form. If you need to distinguish + /horses%2Fponies from /horses/ponies, use + %{THE_REQUEST} in a RewriteCond — it preserves the + original request line exactly as the client sent it, before any + decoding:

      + + +# Match only the literally-encoded %2F, not a real path separator +RewriteCond "%{THE_REQUEST}" "/horses%2F" +RewriteRule "^/horses/ponies$" "/special-handler" [L] + + +

      After substitution, mod_rewrite re-encodes the + resulting URL-path for output. Several flags control this behavior:

      + +
        +
      • [B] — re-escape + backreferences so that special characters captured from the + decoded URL-path are not interpreted as delimiters in the + substitution.
      • + +
      • [BNP] — when [B] is + active, encode spaces as %20 rather than + + (appropriate for path components, not query + strings).
      • + +
      • [NE] — suppress the + default escaping of special characters in the substitution + result, allowing literal #, ?, and + other characters to pass through unmodified on external + redirects.
      • +
      + +
      + AllowEncodedSlashes + +

      By default, Apache returns 404 for any URL containing an encoded + slash (%2F). The AllowEncodedSlashes directive controls + this behavior:

      + +
        +
      • Off (default) — reject %2F with + 404.
      • +
      • On — allow %2F and decode it to + / before passing to handlers.
      • +
      • NoDecode — allow %2F but keep it + in encoded form, letting the backend application distinguish it + from a real path separator.
      • +
      + +

      When using the [B] flag with + URLs that may contain encoded slashes, you typically need + AllowEncodedSlashes NoDecode to prevent Apache from + rejecting the re-encoded result.

      + +
      @@ -142,7 +210,7 @@ and URL matching.

      engine is started with the contained ruleset (one or more rules together with their conditions). The operation of the URL rewriting engine itself is exactly the same for both - configuration contexts. Only the final result processing is + configuration contexts. Only the final result processing is different.

      diff --git a/docs/manual/rewrite/vhosts.xml b/docs/manual/rewrite/vhosts.xml index 8a9b677b72..a270d264ab 100644 --- a/docs/manual/rewrite/vhosts.xml +++ b/docs/manual/rewrite/vhosts.xml @@ -42,10 +42,11 @@ mod_rewrite document. Module documentation mod_rewrite introduction Redirection and remapping - +Per-directory Rewrites +RewriteRule Flags RewriteMap -Per-directory rewrites (.htaccess) When not to use mod_rewrite +Technical details