<seealso><a href="../mod/mod_rewrite.html">Module documentation</a></seealso>
<seealso><a href="intro.html">mod_rewrite introduction</a></seealso>
<seealso><a href="remapping.html">Redirection and remapping</a></seealso>
+<seealso><a href="htaccess.html">Per-directory Rewrites</a></seealso>
+<seealso><a href="flags.html">RewriteRule Flags</a></seealso>
<seealso><a href="vhosts.html">Virtual hosts</a></seealso>
<seealso><a href="rewritemap.html">Using RewriteMap</a></seealso>
-<!--<seealso><a href="htaccess.html">Per-directory rewrites (.htaccess)</a></seealso>
-<seealso><a href="avoid.html">When not to use mod_rewrite</a></seealso>-->
+<seealso><a href="tech.html">Technical details</a></seealso>
<section id="redirect">
<title>Simple Redirection</title>
<a href="remapping.html#canonicalhost">Canonical Hostnames</a>
recipe.</p>
-<p>To redirect <code>http</code> URLs to <code>https</code>, do the
-following:</p>
-
-<highlight language="config">
-<VirtualHost *:80>
- ServerName www.example.com
- Redirect "/" "https://www.example.com/"
-</VirtualHost>
-
-<VirtualHost *:443>
- ServerName www.example.com
- # ... SSL configuration goes here
-</VirtualHost>
-</highlight>
-
-<p>The use of <code>RewriteRule</code> to perform this task may be
-appropriate if there are other <code>RewriteRule</code> directives in
-the same scope. This is because, when there are <code>Redirect</code>
-and <code>RewriteRule</code> directives in the same scope, the
-<code>RewriteRule</code> directives will run first, regardless of the
-order of appearance in the configuration file.</p>
-
-<p>In the case of the <em>http-to-https</em> redirection, the use of
-<code>RewriteRule</code> would be appropriate if you don't have access
-to the main server configuration file, and are obliged to perform this
-task in a <code>.htaccess</code> file instead.</p>
+<p>To redirect <code>http</code> URLs to <code>https</code>, a
+<directive module="mod_alias">Redirect</directive> in a dedicated
+HTTP virtual host is the cleanest approach. See the
+<a href="remapping.html#https-redirect">Forcing HTTPS</a> recipe for
+the recommended configuration and the
+<module>mod_rewrite</module> alternative for <code>.htaccess</code>
+use.</p>
+
+ <note><title>Processing order</title>
+ <p>If you do mix <directive module="mod_alias">Redirect</directive>
+ and <directive module="mod_rewrite">RewriteRule</directive> in the
+ same context, be aware that their execution order depends on where
+ they appear. In server/virtual-host context,
+ <module>mod_rewrite</module> runs first; in per-directory context
+ (<code>.htaccess</code>), <module>mod_alias</module> runs first. See
+ <a href="tech.html#order">Module Processing Order</a> for
+ details.</p>
+ </note>
</section>
<section id="alias"><title>URL Aliasing</title>
<p>The <directive module="mod_alias">Alias</directive> 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 <directive module="core">DocumentRoot</directive>. Although it
is possible to perform this mapping with <module>mod_rewrite</module>,
<directive module="mod_alias">Alias</directive> is the preferred method, for
<seealso><a href="../mod/mod_rewrite.html">Module documentation</a></seealso>
<seealso><a href="intro.html">mod_rewrite introduction</a></seealso>
<seealso><a href="remapping.html">Redirection and remapping</a></seealso>
+<seealso><a href="htaccess.html">Per-directory Rewrites</a></seealso>
<seealso><a href="vhosts.html">Virtual hosts</a></seealso>
<seealso><a href="rewritemap.html">Using RewriteMap</a></seealso>
<seealso><a href="avoid.html">When not to use mod_rewrite</a></seealso>
+<seealso><a href="tech.html">Technical details</a></seealso>
<section id="introduction"><title>Introduction</title>
<p>A <directive module="mod_rewrite">RewriteRule</directive> can have
<p>To limit the characters escaped this way, see <a href="#flag_bne">#flag_bne</a>
and <a href="#flag_bctls">#flag_bctls</a></p>
+
+<p>See <a href="tech.html#encoding">URL Encoding and Decoding</a> for
+a full explanation of how Apache decodes URIs before pattern matching.</p>
</section>
+
<section id="flag_bnp"><title>BNP|backrefnoplus (don't escape space to +)</title>
<p>The [BNP] flag instructs <directive
module="mod_rewrite">RewriteRule</directive> to escape the space character
<p>This flag is available in version 2.4.26 and later.</p>
+
+<p>See <a href="tech.html#encoding">URL Encoding and Decoding</a> for
+background on how encoding is handled in the rewrite pipeline.</p>
</section>
+
<section id="flag_bctls"><title>BCTLS</title>
<p>The [BCTLS] flag is similar to the [B] flag, but only escapes
control characters and the space character. This is the same set of
the next rule, and any other rules that are chained together, are
skipped.</p>
+<highlight language="config">
+# 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]
+</highlight>
+
+<p>Without the [C] flag, the second rule would also match requests
+that arrive at <code>/catalog/</code> directly. The chain ensures the
+second rule is only applied when the first rule matched.</p>
+
</section>
<section id="flag_co"><title>CO|cookie</title>
<dt>samesite</dt>
<dd>If set to anything other than <code>false</code> or <code>0</code>, the <code>SameSite</code>
attribute is set to the specified value. Typical values are <code>None</code>,
-<code>Lax</code>, and <code>Strict</code>. Available in 2.5.1 and later.</dd>
+<code>Lax</code>, and <code>Strict</code>. Available in 2.4.47 and later.</dd>
</dl>
</section>
<section id="flag_dpi"><title>DPI|discardpath</title>
-<p>The DPI flag causes the PATH_INFO that was appended to the rewritten
-URI to be discarded.</p>
-<p>In per-directory context, the URI that each <directive>RewriteRule</directive>
-compares against is the concatenation of the current values of the URI
-and PATH_INFO.</p>
-
-<p>The current URI can be the initial URI as requested by the client, the
-result of a previous round of <module>mod_rewrite</module> processing, or the result of
-a prior rule in the current round of <module>mod_rewrite</module> processing.</p>
-
-<p>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
-<module>mod_rewrite</module> processing. As a consequence, if large portions
-of the URI are matched and copied into a substitution in multiple
+<p>The DPI flag causes the <glossary ref="pathinfo">PATH_INFO</glossary>
+that was appended to the rewritten
+<glossary ref="urlpath">URL-path</glossary> to be discarded.</p>
+
+<p>In <glossary ref="perdirectory">per-directory context</glossary>, the
+<glossary ref="urlpath">URL-path</glossary> each
+<directive>RewriteRule</directive> compares against is the concatenation
+of the current URL-path and PATH_INFO.</p>
+
+<p>The current URL-path can be the initial path as requested by the
+client, the result of a previous round of <module>mod_rewrite</module>
+processing, or the result of a prior rule in the current round of
+<module>mod_rewrite</module> processing.</p>
+
+<p>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
+<module>mod_rewrite</module> processing. As a consequence, if large
+portions of the URL-path are matched and copied into a substitution in multiple
<directive>RewriteRule</directive> 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.</p>
+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.</p>
<p>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
completes. Subsequent rules during this round of processing will see
only the direct result of substitutions, without any PATH_INFO
appended.</p>
+
+<highlight language="config">
+# 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]
+</highlight>
+
+<p>The [DPI] flag discards <code>/extra/path</code> so that only the
+substitution result (<code>/new-app/handler</code>) is passed to
+subsequent rules or the final request.</p>
+
</section>
<section id="flag_e"><title>E|env</title>
CustomLog directives.</p>
<p>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.</p>
<p>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_". </p>
+cookie.</p>
+
+ <note><title>REDIRECT_ prefix after internal redirects</title>
+ <p>In <glossary ref="perdirectory">per-directory context</glossary>,
+ a successful substitution triggers an internal redirect. When this
+ happens, all environment variables set during the previous pass —
+ including those created with <code>[E=VAR:VAL]</code> — are renamed
+ with a <code>REDIRECT_</code> prefix. A variable you set as
+ <code>rewritten</code> becomes <code>REDIRECT_rewritten</code> in
+ the redirected request.</p>
+
+ <p>To test for the renamed variable, reference it with the
+ prefix:</p>
+ </note>
+
+<highlight language="config">
+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]
+</highlight>
+
+ <p>If the request is redirected multiple times, the prefix stacks:
+ <code>REDIRECT_REDIRECT_rewritten</code>, and so on. See
+ <a href="../env.html#redirect-vars">REDIRECT_ variables</a> for
+ the complete description of this mechanism.</p>
+
</section>
<section id="flag_end"><title>END</title>
<p>This does not apply to new requests resulting from external
redirects.</p>
-<p>See the <a href="htaccess.html#loops">.htaccess looping</a>
+<p>See the <a href="htaccess.html#loops">Per-directory Rewrites</a>
discussion for a detailed explanation of why [L] behaves differently
in per-directory context, and when [END] is the right choice.</p>
</highlight>
<p>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.</p>
+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.</p>
<p>When using [F], an [L] is implied - that is, the response is returned
immediately, and no further rules are evaluated.</p>
C. Use this flag to indicate that the current rule should be applied
immediately without considering further rules.</p>
-<p>If you are using <directive
-module="mod_rewrite">RewriteRule</directive> in either
-<code>.htaccess</code> files or in
-<directive type="section" module="core">Directory</directive> 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 <code>.htaccess</code> file or
-<directive type="section" module="core">Directory</directive> 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.</p>
-
-<p>It is therefore important, if you are using <directive
-module="mod_rewrite">RewriteRule</directive> 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.</p>
-
-<p> 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.</p>
+<p>In <glossary ref="perdirectory">per-directory</glossary> 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 <a href="#flag_end">[END]</a> flag to prevent this, or see
+the <a href="htaccess.html#loops">Per-directory Rewrites</a> document
+for a full discussion of the issue and alternative solutions.</p>
<p>The example given here will rewrite any request to
<code>index.php</code>, giving the original request as a query string
RewriteRule "(.*)A(.*)" "$1B$2" [N]
</highlight>
<p>You can think of this as a <code>while</code> 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
<code>A</code>), perform this substitution (i.e., replace the
<code>A</code> with a <code>B</code>).</p>
-<p>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. </p>
-<highlight language="config">
-# 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]
-</highlight>
+<p>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.</p>
</section>
<p>Use of the [NC] flag causes the <directive
module="mod_rewrite">RewriteRule</directive> 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.</p>
+as upper-case or lower-case in the matched URL-path.</p>
<p>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
then result in a 404 Not Found error condition.
</p>
+<p>See <a href="tech.html#encoding">URL Encoding and Decoding</a> for
+the full picture of how Apache encodes and decodes URIs during
+rewriting.</p>
</section>
<section id="flag_ns"><title>NS|nosubreq</title>
are not subrequests - the browser requests them as separate HTTP
requests.
</p>
+
+<highlight language="config">
+# 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]
+</highlight>
+
</section>
<section id="flag_p"><title>P|proxy</title>
considered.</p>
<p>
-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 <code>http://</code><em>hostname</em>) which can be
handled by the <module>mod_proxy</module>. If not, you will get an
error from the proxy module. Use this flag to achieve a
<note type="warning">
<title>Security Warning</title>
<p>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.</p>
+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.</p>
</note>
<note type="warning">
<p>Note: <module>mod_proxy</module> must be enabled in order
to use this flag.</p>
-<note type="warning"><title>Security warning</title>
-<p>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.</p>
-</note>
-
</section>
<section id="flag_pt"><title>PT|passthrough</title>
<p>
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 <directive
module="mod_rewrite">RewriteRule</directive> to be passed back through
URL mapping, so that location-based mappings, such as <directive
<section id="flag_qsa"><title>QSA|qsappend</title>
<p>
-When the replacement URI contains a query string, the default behavior
+When the replacement URL contains a query string, the default behavior
of <directive module="mod_rewrite">RewriteRule</directive> 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.
<section id="flag_qsd"><title>QSD|qsdiscard</title>
<p>
-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 <directive
module="mod_rewrite">RewriteRule</directive> 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.
</p>
</p>
<p>
-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 <code>RewriteRule</code> target
-URI.
+URL.
</p>
+<highlight language="config">
+# 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]
+</highlight>
+
</section>
<section id="flag_qsl"><title>QSL|qslast</title>
<p>
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
-<code>http://thishost[:thisport]</code> to the URI, but then passes this
+<code>http://thishost[:thisport]</code> 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.
</p>
spec. Using an unrecognized status code will result in a 500 error and
error log message.</p>
+<note type="warning"><title>[R=4xx] does not serve the substitution</title>
+<p>When a status code outside the 300-399 range is specified (e.g.,
+<code>[R=403]</code> or <code>[R=410]</code>), the
+<em>substitution string is ignored</em>. 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
+<directive module="core">ErrorDocument</directive>).
+If you want to deny access, the <a href="#flag_f">[F]</a> and
+<a href="#flag_g">[G]</a> flags are clearer ways to express the
+same intent.</p>
+</note>
+
+<highlight language="config">
+# Redirect requests for the old docs path to the new location.
+RewriteRule "^/docs/(.*)$" "http://docs.example.com/$1" [R=301,L]
+</highlight>
+
</section>
<section id="flag_s"><title>S|skip</title>
RewriteCond</directive> directives match). This can be thought of as a
<code>goto</code> statement in your rewrite ruleset. In the following
example, we only want to run the <directive module="mod_rewrite">
-RewriteRule</directive> if the requested URI doesn't correspond with an
+RewriteRule</directive> if the requested URL-path doesn't correspond with an
actual file.</p>
<highlight language="config">
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.</p>
+
+<highlight language="config">
+# 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]
+</highlight>
+
+<note type="warning">
+This flag exists because of <a
+href="https://www.cve.org/CVERecord?id=CVE-2024-38474">CVE-2024-38474</a>.
+Use it only on rules where you are certain that user-supplied <code>%3F</code>
+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.
+</note>
+
</section>
<section id="flag_unsafe_prefix_stat"><title>UnsafePrefixStat</title>
<p> Setting this flag is required in server-scoped substitutions
This protects from a malicious URL causing the expanded substitution to
map to an unexpected filesystem location.</p>
- <p><since>2.5.1</since></p>
+ <p><since>2.4.60</since></p>
+
+<highlight language="config">
+# 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]
+</highlight>
+
+<note type="warning">
+This flag exists because of <a
+href="https://www.cve.org/CVERecord?id=CVE-2024-38475">CVE-2024-38475</a>.
+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.
+</note>
+
</section>
<section id="flag_unc"><title>UNC</title>
<p> 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.</p>
- <p><since>2.5.1</since></p>
+ <p><since>2.4.63</since></p>
+
+<highlight language="config">
+# 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]
+</highlight>
+
+<p>This flag is only relevant on Windows. It prevents Apache from
+merging the leading double slash (<code>//</code>) 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.</p>
+
</section>
</manualpage>
<manualpage metafile="htaccess.xml.meta">
<parentdocument href="./">Rewrite</parentdocument>
-<title>mod_rewrite and .htaccess files</title>
+<title>Per-directory Rewrites</title>
<summary>
in either the pattern or the substitution. This is the single most
common source of confusion with per-directory rewriting.</p>
+<p>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 <directive module="mod_rewrite">RewriteBase</directive> directive
+determines what URL-path prefix is prepended. This subrequest
+mechanism is also why rules can loop — see
+<a href="#loops">below</a>.</p>
+
+
+<note type="warning"><title>Do not start .htaccess patterns with /</title>
+<p>Because the directory prefix (including the trailing
+slash) is stripped before matching, patterns in
+<glossary ref="perdirectory">per-directory context</glossary>
+will <em>never</em> match a leading slash. A pattern
+beginning with <code>^/</code> will never match in this
+context. A rule like <code>RewriteRule "^/foo" ...</code> will
+silently fail to match anything when placed in a
+<code>.htaccess</code> file.</p>
+</note>
+
+<p>If you need to match against the full original
+URL-path (including the directory prefix), use
+<code>%{REQUEST_URI}</code> in a
+<directive module="mod_rewrite">RewriteCond</directive>:</p>
+
+<highlight language="config">
+RewriteCond "%{REQUEST_URI}" "^/admin/"
+RewriteRule "^.*$" "-" [F]
+</highlight>
+
</section>
<section id="rewritebase"><title>When you need RewriteBase</title>
<note>For this particular use case - routing all unmatched
requests to a front controller - the
-<a href="remapping.html#fallback-resource">FallbackResource</a> directive
+<a href="avoid.html#fallback-resource">FallbackResource</a> directive
is a simpler and more efficient alternative to mod_rewrite.</note>
<p>Without the <code>RewriteBase "/myapp/"</code> line, the rewritten
</section>
+<section id="context-restrictions"><title>Which contexts support rewrite rules?</title>
+
+<p>Rewrite rules are supported in
+<glossary ref="perdirectory">per-directory context</glossary>
+(<a href="../howto/htaccess.html">.htaccess</a> files,
+<directive module="core" type="section">Directory</directive>, and
+<directive module="core" type="section">If</directive> blocks).</p>
+
+<p>Although rewrite rules are syntactically permitted in
+<directive module="core" type="section">Location</directive>
+and <directive module="core" type="section">Files</directive>
+sections (including their regex counterparts), this is
+unsupported and should never be necessary. Relative
+substitutions, in particular, are likely to break in
+these contexts.</p>
+
+<note type="warning"><title>These containers silently change rewrite
+behavior</title>
+<p>Placing a <directive module="mod_rewrite">RewriteRule</directive>
+inside a <directive module="core" type="section">Directory</directive>,
+<directive module="core" type="section">If</directive>, or
+<directive module="core" type="section">Location</directive> block
+— even inside a
+<directive module="core" type="section">VirtualHost</directive> in the
+main server config — silently switches to
+<glossary ref="perdirectory">per-directory context</glossary> behavior.
+This means the leading slash is stripped from the URL before pattern
+matching, substitutions trigger an internal redirect (with loop risk),
+and the <a href="../rewrite/flags.html#flag_l">[L]</a> flag no longer
+truly stops processing — use
+<a href="../rewrite/flags.html#flag_end">[END]</a> instead.</p>
+</note>
+
+</section>
+
<section id="inheritance"><title>Rule inheritance with RewriteOptions</title>
<p>By default, <module>mod_rewrite</module> rules are <strong>not
</section>
-<section id="context-restrictions"><title>Which contexts support rewrite rules?</title>
-
-<p>Rewrite rules are supported in
-<glossary ref="perdirectory">per-directory context</glossary>
-(<a href="../howto/htaccess.html">.htaccess</a> files,
-<directive module="core" type="section">Directory</directive>, and
-<directive module="core" type="section">If</directive> blocks).</p>
-
-<p>Although rewrite rules are syntactically permitted in
-<directive module="core" type="section">Location</directive>
-and <directive module="core" type="section">Files</directive>
-sections (including their regex counterparts), this is
-unsupported and should never be necessary. Relative
-substitutions, in particular, are likely to break in
-these contexts.</p>
-
-<note type="warning"><title>These containers silently change rewrite
-behavior</title>
-<p>Placing a <directive module="mod_rewrite">RewriteRule</directive>
-inside a <directive module="core" type="section">Directory</directive>,
-<directive module="core" type="section">If</directive>, or
-<directive module="core" type="section">Location</directive> block
-— even inside a
-<directive module="core" type="section">VirtualHost</directive> in the
-main server config — silently switches to
-<glossary ref="perdirectory">per-directory context</glossary> behavior.
-This means the leading slash is stripped from the URL before pattern
-matching, substitutions trigger an internal redirect (with loop risk),
-and the <a href="../rewrite/flags.html#flag_l">[L]</a> flag no longer
-truly stops processing — use
-<a href="../rewrite/flags.html#flag_end">[END]</a> instead.</p>
-</note>
+<section id="caching"><title>Browser caching of 301 redirects</title>
+
+<p>When you issue a <code>301 Moved Permanently</code> redirect
+(via <code>[R=301]</code> or <directive module="mod_alias"
+>Redirect permanent</directive>), 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.</p>
+
+<p>While debugging redirect rules, use <code>[R=302]</code>
+(temporary redirect) instead of <code>[R=301]</code>. 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.</p>
+
+<note>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.</note>
</section>
<summary>
+<p><em>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.</em>
+-- Brian Behlendorf</p>
+
+<p><em>Despite the tons of examples and docs, mod_rewrite is voodoo.
+Damned cool voodoo, but still voodoo.</em>
+-- Brian Moore</p>
+
<p><module>mod_rewrite</module> provides a way to modify incoming
URL requests, dynamically, based on <a href="intro.html#regex">regular
expression</a> rules. This allows you to map arbitrary URLs onto
your internal URL structure in any way you like.</p>
- <p>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.</p>
-
- <p>Rewrite rules can operate on the full URLs, including the path-info
- and query string portions, and may be used in per-server context
- (<code>httpd.conf</code>), per-virtualhost context (<directive
- type="section" module="core">VirtualHost</directive> blocks), or
- <glossary ref="perdirectory">per-directory context</glossary>
- (<code>.htaccess</code> files and <directive
- type="section" module="core">Directory</directive> blocks). The
- rewritten result can lead to further rules, internal
- sub-processing, external request redirection, or proxy
- passthrough, depending on what <a href="flags.html">flags</a> you
- attach to the rules.</p>
-
- <p>Since <module>mod_rewrite</module> is so powerful, it can indeed be rather
- complex. This guide supplements the <a
- href="../mod/mod_rewrite.html">reference documentation</a>, and
- attempts to allay some of that complexity, and provide highly
- annotated examples of common scenarios that you may handle with
- <module>mod_rewrite</module>. But we also attempt to show you when you should not
- use <module>mod_rewrite</module>, and use other standard Apache features instead,
- thus avoiding this unnecessary complexity.</p>
-
-
-<ul>
-<li><a href="../mod/mod_rewrite.html">mod_rewrite reference
-documentation</a></li>
-<li><a href="intro.html">Introduction to regular expressions and mod_rewrite</a></li>
-<li><a href="htaccess.html">mod_rewrite in .htaccess files</a></li>
-<li><a href="flags.html">RewriteRule Flags</a></li>
-<li><a href="rewritemap.html">Using RewriteMap</a></li>
-<li><a href="avoid.html">When <strong>NOT</strong> to use mod_rewrite</a></li>
-<li><a href="remapping.html">Using mod_rewrite for redirection and remapping of URLs</a></li>
-<li><a href="vhosts.html">Dynamic virtual hosts with mod_rewrite</a></li>
-<li><a href="tech.html">Technical details</a></li>
-</ul>
+ <p>This guide supplements <a href="../mod/mod_rewrite.html">the
+ reference manual</a> with annotated examples, conceptual
+ explanations, and practical advice. It is organized as follows:</p>
+
+<dl>
+<dt><a href="intro.html">Introduction</a></dt>
+<dd>Core concepts: regular expression syntax, RewriteRule and
+RewriteCond basics, and how mod_rewrite fits into the request
+processing lifecycle.</dd>
+
+<dt><a href="htaccess.html">Per-directory Rewrites</a></dt>
+<dd>The key differences between using rewrite rules in server
+configuration versus <glossary ref="perdirectory">per-directory</glossary>
+context, including path stripping, RewriteBase, and the looping behavior
+of the [L] flag.</dd>
+
+<dt><a href="flags.html">RewriteRule Flags</a></dt>
+<dd>A complete reference for all flags that can modify the behavior of
+a RewriteRule, with examples for each.</dd>
+
+<dt><a href="rewritemap.html">Using RewriteMap</a></dt>
+<dd>How to use external lookup sources — text files, DBM databases,
+SQL queries, and internal functions — to drive your rewrite
+rules.</dd>
+
+<dt><a href="remapping.html">Redirection and Remapping</a></dt>
+<dd>Recipes for common tasks: HTTPS redirection, canonical hostnames,
+trailing slash normalization, front-controller routing, and more.</dd>
+
+<dt><a href="vhosts.html">Dynamic Virtual Hosts</a></dt>
+<dd>Using mod_rewrite to dynamically map hostnames to document roots
+without individual VirtualHost blocks.</dd>
+
+<dt><a href="avoid.html">When NOT to use mod_rewrite</a></dt>
+<dd>Many common tasks are better accomplished with simpler directives.
+This document shows the alternatives and when to prefer them.</dd>
+
+<dt><a href="tech.html">Technical Details</a></dt>
+<dd>How mod_rewrite hooks into the Apache request processing phases,
+and the order in which rules and conditions are evaluated.</dd>
+</dl>
</summary>
-<seealso><a href="../mod/mod_rewrite.html">mod_rewrite reference
-documentation</a></seealso>
+<seealso><a href="../mod/mod_rewrite.html">mod_rewrite Reference Documentation</a></seealso>
<seealso><a href="../urlmapping.html">Mapping URLs to the Filesystem</a></seealso>
<seealso><a href="https://cwiki.apache.org/confluence/display/httpd/Rewrite">mod_rewrite
wiki</a></seealso>
</summary>
<seealso><a href="../mod/mod_rewrite.html">Module documentation</a></seealso>
-<!-- <seealso><a href="intro.html">mod_rewrite introduction</a></seealso> -->
<seealso><a href="remapping.html">Redirection and remapping</a></seealso>
+<seealso><a href="htaccess.html">Per-directory Rewrites</a></seealso>
+<seealso><a href="flags.html">RewriteRule Flags</a></seealso>
<seealso><a href="vhosts.html">Virtual hosts</a></seealso>
<seealso><a href="rewritemap.html">Using RewriteMap</a></seealso>
<seealso><a href="avoid.html">When not to use mod_rewrite</a></seealso>
+<seealso><a href="tech.html">Technical details</a></seealso>
<section id="introduction"><title>Introduction</title>
<p>The Apache module <module>mod_rewrite</module> is a very powerful and
<section id="regex"><title>Regular Expressions</title>
-<p><module>mod_rewrite</module> uses the <a href="https://www.pcre.org/">Perl Compatible
-Regular Expression</a> vocabulary. In this document, we do not attempt
+<p><module>mod_rewrite</module> uses the <a href="https://www.pcre.org/current/doc/html/pcre2syntax.html">Perl Compatible
+Regular Expressions</a> 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 <a href="https://www.pcre.org/pcre.txt">PCRE man pages</a>, the
+recommend the <a href="https://www.pcre.org/current/doc/html/pcre2syntax.html">PCRE2 documentation</a>, the
<a href="https://perldoc.perl.org/perlre">Perl regular
-expression man page</a>, and <a
+expressions man page</a>, and <a
href="https://www.oreilly.com/library/view/mastering-regular-expressions/0596528124/">Mastering
Regular Expressions, by Jeffrey Friedl</a> (the third edition is from
2006, but regular expression syntax is essentially unchanged, and this
</ol>
<p>The <var>Pattern</var> is a <a href="#regex">regular expression</a>.
-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 <a href="../directive-dict.html#Syntax">URL-path</a>
+of the incoming request — the part after the hostname and port,
+and not including the query string (e.g., <code>/app/index.html</code>).
+In <glossary ref="perdirectory">per-directory context</glossary>,
+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 <a href="htaccess.html#path-stripping">Per-directory
+Rewrites</a> for details).
</p>
+<p>Once a substitution has occurred, any rules that follow are
+matched against the substituted value.</p>
+
+<p>The <var>Pattern</var> is matched only against the URL-path
+— not the hostname, port, or query string. To match against
+those, use a
+<directive module="mod_rewrite">RewriteCond</directive> with the
+<code>%{HTTP_HOST}</code>, <code>%{SERVER_PORT}</code>, or
+<code>%{QUERY_STRING}</code> variables respectively.</p>
+
+<note><module>mod_rewrite</module> 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 <module>mod_request</module> paired with a custom filter.</note>
+
<p class="figure">
<img src="../images/syntax_rewriterule.png"
alt="Syntax of the RewriteRule directive" /><br />
<section id="htaccess"><title>.htaccess files</title>
-<p>Rewriting is typically configured in the main server configuration
-setting (outside any <directive type="section"
-module="core">Directory</directive> section) or
-inside <directive type="section" module="core">VirtualHost</directive>
-containers. This is the easiest way to do rewriting and is
-recommended. It is possible, however, to do rewriting
-inside <directive type="section" module="core">Directory</directive>
-sections or <a href="../howto/htaccess.html"><code>.htaccess</code>
-files</a> at the expense of some additional complexity. This technique
-is called <glossary ref="perdirectory">per-directory</glossary> rewrites.
-Note that
+<p>It is possible to use rewrite rules in
+<glossary ref="perdirectory">per-directory</glossary> context
+(<a href="../howto/htaccess.html"><code>.htaccess</code> files</a> and
+<directive type="section" module="core">Directory</directive> blocks),
+but the rules behave differently there — in particular, the directory
+prefix is stripped from the URL before matching. See the
+<a href="htaccess.html#path-stripping">Per-directory Rewrites</a>
+document for full details. Note that
<directive module="core" type="section">If</directive> and
<directive module="core" type="section">Location</directive> blocks
also trigger per-directory behavior — see
<a href="htaccess.html#context-restrictions">Which contexts support rewrite rules?</a>.</p>
-<p>The main difference from per-server rewrites is that the path
-prefix of the directory containing the <code>.htaccess</code> file is
-stripped before matching in
-the <directive module="mod_rewrite">RewriteRule</directive>. In addition, the <directive module="mod_rewrite">RewriteBase</directive> should be used to assure the request is properly mapped.</p>
-
</section>
<section id="security"><title>Security Considerations</title>
</summary>
<seealso><a href="../mod/mod_rewrite.html">Module documentation</a></seealso>
<seealso><a href="intro.html">mod_rewrite introduction</a></seealso>
-<!--<seealso><a href="remapping.html">Redirection and remapping</a></seealso>-->
+<seealso><a href="htaccess.html">Per-directory Rewrites</a></seealso>
+<seealso><a href="flags.html">RewriteRule Flags</a></seealso>
<seealso><a href="vhosts.html">Virtual hosts</a></seealso>
<seealso><a href="rewritemap.html">Using RewriteMap</a></seealso>
<seealso><a href="avoid.html">When not to use mod_rewrite</a></seealso>
+<seealso><a href="tech.html">Technical details</a></seealso>
<section id="old-to-new">
<p>See also the <a href="avoid.html#redirect">When not to use
mod_rewrite</a> document for more discussion of the
<code>Redirect</code> approach.</p>
+
+ <note><title>Behind a load balancer or SSL terminator</title>
+ <p>The <code>%{HTTPS}</code> variable is not a general-purpose
+ environment variable — it queries <module>mod_ssl</module>
+ directly. If SSL/TLS is terminated at an upstream load balancer
+ or reverse proxy, <module>mod_ssl</module> is not handling the
+ connection and <code>%{HTTPS}</code> will always report
+ <code>off</code>, even when the original client connected over
+ HTTPS.</p>
+
+ <p>In this situation, check the header set by the upstream proxy
+ instead. Most load balancers set
+ <code>X-Forwarded-Proto</code>:</p>
+ </note>
+
+<highlight language="config">
+RewriteEngine On
+RewriteCond "%{HTTP:X-Forwarded-Proto}" =http [NC]
+RewriteRule "^(.*)" "https://%{SERVER_NAME}$1" [R=301,L]
+</highlight>
+
+ <note type="warning">
+ <p>Only trust <code>X-Forwarded-Proto</code> 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
+ <module>mod_remoteip</module> to validate the source.</p>
+ </note>
+
</dd>
</dl>
</section>
-<section id="trailing-slash">
+<section id="acme-exemption">
- <title>Trailing Slash Normalization</title>
+ <title>Exempting ACME challenge requests from HTTPS redirect</title>
<dl>
<dt>Description:</dt>
<dd>
- <p>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.</p>
+ <p>You have forced all traffic to HTTPS (as above), but your
+ ACME client (Let's Encrypt, Certbot, etc.) needs plain HTTP
+ access to <code>/.well-known/acme-challenge/</code> to complete
+ domain validation.</p>
</dd>
<dt>Solution:</dt>
<dd>
- <p>To add a trailing slash to URLs that map to directories:</p>
+ <p>Place an exception <em>before</em> your HTTPS redirect
+ rule:</p>
<highlight language="config">
-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]
</highlight>
+ </dd>
- <p>To remove a trailing slash (except for actual directories):</p>
+ <dt>Discussion:</dt>
+
+ <dd>
+ <p>The dash (<code>-</code>) substitution means "do not rewrite."
+ Combined with <code>[L]</code>, 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.</p>
+
+ <p>If you are using the <directive
+ module="mod_alias">Redirect</directive> approach in a dedicated
+ port-80 VirtualHost, use an
+ <directive module="mod_alias">Alias</directive> and
+ <directive module="mod_alias">RedirectMatch</directive>
+ instead:</p>
<highlight language="config">
-RewriteCond "%{REQUEST_FILENAME}" !-d
-RewriteCond "%{REQUEST_URI}" "(.+)/$"
-RewriteRule "^" "%1" [R=301,L]
-</highlight>
+<VirtualHost *:80>
+ ServerName www.example.com
- </dd>
+ # 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>
- <dt>Discussion:</dt>
+ # Everything else goes to HTTPS
+ RedirectMatch permanent "^/(?!\.well-known/acme-challenge/)" "https://www.example.com/$0"
+</VirtualHost>
+</highlight>
- <dd>
- <p>Apache's <module>mod_dir</module> already handles trailing
- slash redirects for real directories when
- <directive module="mod_dir">DirectorySlash</directive> is enabled
- (the default). You only need a <module>mod_rewrite</module> 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.</p>
</dd>
</dl>
</section>
-<section id="www-resolve">
+<section id="trailing-slash">
- <title>Canonical www/non-www Hostname</title>
+ <title>Trailing Slash Normalization</title>
<dl>
<dt>Description:</dt>
<dd>
- <p>You want to force all requests to use either
- <code>www.example.com</code> or <code>example.com</code>,
- not both. This ensures search engines treat them as one site
- and prevents cookie scope issues.</p>
+ <p>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.</p>
</dd>
<dt>Solution:</dt>
<dd>
- <p>The best approach does not use <module>mod_rewrite</module> at
- all. Place a <directive module="mod_alias">Redirect</directive>
- in the virtual host for the non-canonical hostname:</p>
+ <p>To add a trailing slash to URLs that map to directories:</p>
<highlight language="config">
-# 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]
</highlight>
- <p>If you only have <code>.htaccess</code> access:</p>
-
-<highlight language="config">
-# Add www
-RewriteEngine On
-RewriteCond "%{HTTP_HOST}" "!^www\." [NC]
-RewriteRule "^(.*)" "https://www.%{HTTP_HOST}$1" [R=301,L]
-</highlight>
+ <p>To remove a trailing slash (except for actual directories):</p>
<highlight language="config">
-# 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]
</highlight>
</dd>
<dt>Discussion:</dt>
<dd>
- <p>See also the <a href="#canonicalhost">Canonical Hostnames</a>
- 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.</p>
+ <p>Apache's <module>mod_dir</module> already handles trailing
+ slash redirects for real directories when
+ <directive module="mod_dir">DirectorySlash</directive> is enabled
+ (the default). You only need a <module>mod_rewrite</module> 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.</p>
</dd>
</dl>
<title>Front Controller / Application Routing</title>
- <dl>
- <dt>Description:</dt>
-
- <dd>
- <p>Most modern web frameworks (PHP, Python, Ruby, etc.) use a
- single entry point - often called a "front controller" - that
- handles all requests. URLs like <code>/products/widget</code>
- are routed to <code>index.php</code> (or equivalent), which
- parses the URL internally.</p>
- </dd>
-
- <dt>Solution:</dt>
-
- <dd>
+ <p>Most modern web frameworks route all requests through a single
+ entry point (a "front controller"). The
+ <directive module="mod_dir">FallbackResource</directive> directive
+ handles this more simply and efficiently than
+ <module>mod_rewrite</module>. See <a
+ href="avoid.html#fallback-resource">When NOT to use mod_rewrite</a>
+ for the recommended approach.</p>
- <note>For this use case, the
- <directive module="mod_dir">FallbackResource</directive> directive is
- almost always the better choice. See the
- <a href="#fallback-resource">Fallback Resource</a> recipe above.</note>
-
- <p>If you need <module>mod_rewrite</module> (for example, to add
- additional conditions), the standard pattern is:</p>
-
-<highlight language="config">
-RewriteEngine On
-RewriteCond "%{REQUEST_FILENAME}" !-f
-RewriteCond "%{REQUEST_FILENAME}" !-d
-RewriteRule "^(.*)$" "/index.php" [L]
-</highlight>
-
- <p>The <code>!-f</code> and <code>!-d</code> conditions skip the
- rule for requests that map to an existing file or directory, so
- static assets (images, CSS, JavaScript) are still served
- directly.</p>
- </dd>
-
- <dt>Discussion:</dt>
-
- <dd>
- <p>In <code>.htaccess</code> context, consider using
- <code>[END]</code> instead of <code>[L]</code> to avoid
- reprocessing loops. See the
- <a href="htaccess.html#loops">.htaccess looping</a> discussion
- for details.</p>
- </dd>
- </dl>
+ <p>If you genuinely need <module>mod_rewrite</module> for this (for
+ example, to add conditions beyond "file doesn't exist"), see the
+ <a href="htaccess.html#rewritebase">per-directory rewrites</a>
+ document for an annotated example that also demonstrates
+ <directive module="mod_rewrite">RewriteBase</directive> usage.</p>
</section>
<strong>example.com</strong>, you could use the following
recipe:</p>
+<p>To do the reverse - strip the <code>www.</code> prefix - swap the
+condition:</p>
+
+<highlight language="config">
+RewriteCond "%{HTTP_HOST}" "^www\.(.+)$" [NC]
+RewriteRule "^(.*)" "http://%1/$1" [L,R,NE]
+</highlight>
+
+<p>To generically add <code>www.</code> to any hostname:</p>
+
<highlight language="config">
RewriteCond "%{HTTP_HOST}" "!^www\." [NC]
RewriteCond "%{HTTP_HOST}" "!^$"
<p>These rulesets will work either in your main server configuration
file, or in a <code>.htaccess</code> file placed in the <directive
module="core">DocumentRoot</directive> of the server.</p>
-
</dd>
-
+
<dt>Discussion:</dt>
<dd>
<p>If you have access to the server configuration, a
<directive module="mod_alias">Redirect</directive> in a dedicated
<directive module="core" type="section">VirtualHost</directive>
- 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.</p>
+ <p>Use the
<directive module="core" type="section">If</directive> directive
as a middle ground, and <module>mod_rewrite</module> only if you
are limited to <code>.htaccess</code>.</p>
</dd>
- </dl>
-
+</dl>
</section>
</section>
-<section id="fallback-resource">
-<title>Fallback Resource</title>
-
-<dl>
-<dt>Description:</dt>
-<dd>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.</dd>
-
-<dt>Solution:</dt>
-<dd>
-<p>As of version 2.2.16, you should use the <directive
-module="mod_dir">FallbackResource</directive> directive for this:</p>
-
-<highlight language="config">
-<Directory "/var/www/my_blog">
- FallbackResource index.php
-</Directory>
-</highlight>
-
-<p>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:</p>
-
-<highlight language="config">
-<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>
-</highlight>
-
-<p>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:</p>
-
-<highlight language="config">
-RewriteRule "(.*)" "index.php?$1" [PT,QSA]
-</highlight>
-
-<p>Note that these rulesets can be used in a <code>.htaccess</code>
-file, as well as in a <Directory> block.</p>
-
-</dd>
-
-<dt>Discussion:</dt>
-
-<dd>
-<p>The <directive module="mod_dir">FallbackResource</directive> directive
-is almost always the better choice for this use case. See the
-<a href="avoid.html#fallback-resource">When not to use mod_rewrite</a>
-document for a simpler one-line alternative.</p>
-</dd>
-
-</dl>
-
-</section>
-
<section id="rewrite-query">
<title>Rewrite query string</title>
</li>
<li>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 <glossary ref="pathinfo">PATH_INFO</glossary>) from the URL into the query string.
<highlight language="config">
# The desired URL might be /products/kitchen-sink, and the script expects
# /path?products=kitchen-sink.
<seealso><a href="../mod/mod_rewrite.html">Module documentation</a></seealso>
<seealso><a href="intro.html">mod_rewrite introduction</a></seealso>
<seealso><a href="remapping.html">Redirection and remapping</a></seealso>
+ <seealso><a href="flags.html">RewriteRule Flags</a></seealso>
<seealso><a href="vhosts.html">Virtual hosts</a></seealso>
- <seealso><a href="htaccess.html">Per-directory rewrites (.htaccess)</a></seealso>
+ <seealso><a href="htaccess.html">Per-directory Rewrites</a></seealso>
<seealso><a href="avoid.html">When not to use mod_rewrite</a></seealso>
+<seealso><a href="tech.html">Technical details</a></seealso>
<section id="introduction">
<title>Introduction</title>
>RewriteRule</directive>:
</p>
- <p> <strong>Redirect a URI to an all-lowercase version of itself</strong></p>
+ <p> <strong>Redirect a URL-path to an all-lowercase version of itself</strong></p>
<highlight language="config">
RewriteMap lc int:tolower
RewriteRule "(.*)" "${lc:$1}" [R]
<directive module="core">Mutex</directive> directive.</p>
<p>A simple example is shown here which will replace all dashes with
- underscores in a request URI.</p>
+ underscores in a request URL-path.</p>
<p><strong>Rewrite configuration</strong></p>
<highlight language="config">
print(line.strip().replace('-', '_'), flush=True)
</highlight>
+ <p>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 <code>STDERR</code>,
+ which ends up in the Apache error log.</p>
+
+ <p><strong>Rewrite configuration</strong></p>
+ <highlight language="config">
+RewriteMap vhost2docroot "prg:/www/bin/vhost_lookup.py"
+RewriteRule "^/(.*)$" "${vhost2docroot:%{HTTP_HOST}}/$1"
+ </highlight>
+
+ <p><strong>vhost_lookup.py</strong></p>
+ <highlight language="python">
+#!/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)
+ </highlight>
+
+
<note><title>Caution!</title>
<ul>
<li>Keep your rewrite map program as simple as possible. If the program
<li>Be sure to turn off buffering in your program. In the Python example
above, this is done by passing <code>flush=True</code> to
<code>print()</code>. Buffered I/O will cause httpd to wait for the
-output, and so it will hang.</li>
+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.</li>
<li>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
<seealso><a href="../mod/mod_rewrite.html">Module documentation</a></seealso>
<seealso><a href="intro.html">mod_rewrite introduction</a></seealso>
<seealso><a href="remapping.html">Redirection and remapping</a></seealso>
+<seealso><a href="flags.html">RewriteRule Flags</a></seealso>
<seealso><a href="vhosts.html">Virtual hosts</a></seealso>
<seealso><a href="rewritemap.html">Using RewriteMap</a></seealso>
-<seealso><a href="htaccess.html">Per-directory rewrites (.htaccess)</a></seealso>
+<seealso><a href="htaccess.html">Per-directory Rewrites</a></seealso>
<seealso><a href="avoid.html">When not to use mod_rewrite</a></seealso>
<section id="InternalAPI"><title>API Phases</title>
<p>In each of these cases, <module>mod_rewrite</module> rewrites the
<code>REQUEST_URI</code> either to a new URL, or to a filename.</p>
- <p>In <glossary ref="perdirectory">per-directory context</glossary> (i.e., within <code>.htaccess</code> files
- and <code>Directory</code> blocks), these rules are being applied
- after a URL has already been translated to a filename. Because of
- this, the URL-path that <module>mod_rewrite</module> initially compares <directive
- module="mod_rewrite">RewriteRule</directive> directives against
- is the full filesystem path to the translated filename with the current
- directories path (including a trailing slash) removed from the front.</p>
-
- <p> 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.</p>
-
- <p> 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 <directive
- module="mod_rewrite">RewriteBase</directive> 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 <a href="https://cwiki.apache.org/confluence/display/httpd/RewriteLooping">RewriteLooping</a>
- for further discussion of this problem.)</p>
-
- <p>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.</p>
-
- <table border="1">
-
- <tr>
- <th>Location of rule</th>
- <th>Rule</th>
- </tr>
-
- <tr>
- <td>VirtualHost section</td>
- <td>RewriteRule "^/images/(.+)\.jpg" "/images/$1.gif"</td>
- </tr>
-
- <tr>
- <td>.htaccess file in document root</td>
- <td>RewriteRule "^images/(.+)\.jpg" "images/$1.gif"</td>
- </tr>
-
- <tr>
- <td>.htaccess file in images directory</td>
- <td>RewriteRule "^(.+)\.jpg" "$1.gif"</td>
- </tr>
-
- </table>
-
- <p>For even more insight into how <module>mod_rewrite</module> manipulates URLs in
- different contexts, you should consult the <a
- href="../mod/mod_rewrite.html#logging">log entries</a> made during
- rewriting.</p>
+ <p>In <glossary ref="perdirectory">per-directory context</glossary>,
+ 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
+ <a href="htaccess.html#path-stripping">Per-directory Rewrites</a>
+ document for practical details on path stripping, RewriteBase, and
+ how to avoid looping.</p>
+
+</section>
+
+<section id="order"><title>Module Processing Order</title>
+
+ <p><module>mod_rewrite</module> and <module>mod_alias</module> both
+ operate during the URL-to-filename translation phase, but
+ <module>mod_rewrite</module> runs <strong>first</strong> 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.</p>
+
+ <p>The practical consequence: when both <directive
+ module="mod_rewrite">RewriteRule</directive> and <directive
+ module="mod_alias">Redirect</directive> (or <directive
+ module="mod_alias">RedirectMatch</directive>) are present in the
+ same server or virtual-host context, the rewrite rules are
+ evaluated first. If a <code>RewriteRule</code> matches and rewrites
+ the URL-path (or returns a redirect), <code>Redirect</code> never sees
+ the request.</p>
+
+ <highlight language="config">
+# 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]
+</highlight>
+
+ <note><title>Per-directory context reverses the order</title>
+ <p>In <glossary ref="perdirectory">per-directory context</glossary>,
+ the situation is different. <module>mod_alias</module> directives like
+ <code>Redirect</code> still run in the URL-to-filename translation
+ phase, but <module>mod_rewrite</module> rules run later, in the
+ Fixup phase. This means that in per-directory context,
+ <code>Redirect</code> is evaluated <em>before</em>
+ <code>RewriteRule</code>.</p>
+ </note>
+
+ <p>Because of this inconsistency between contexts, mixing
+ <module>mod_rewrite</module> and <module>mod_alias</module>
+ 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
+ <code>RewriteRule</code> exclusively. If a simple prefix redirect
+ suffices, use <code>Redirect</code> and don't add rewrite rules
+ that might interact with it.</p>
+
+</section>
+
+<section id="encoding"><title>URL Encoding and Decoding</title>
+
+ <p>Apache httpd unescapes URL-encoded characters in the request URL-path before any
+ <directive module="mod_rewrite">RewriteRule</directive> pattern
+ matching takes place. A request for
+ <code>/my%20page/cats%3Fdogs</code> is decoded to
+ <code>/my page/cats?dogs</code>, and that decoded string is what the
+ <code>RewriteRule</code> pattern matches against.</p>
+
+ <p>This means you cannot write a pattern that matches the literal
+ URL-encoded form. If you need to distinguish
+ <code>/horses%2Fponies</code> from <code>/horses/ponies</code>, use
+ <code>%{THE_REQUEST}</code> in a <directive
+ module="mod_rewrite">RewriteCond</directive> — it preserves the
+ original request line exactly as the client sent it, before any
+ decoding:</p>
+
+<highlight language="config">
+# Match only the literally-encoded %2F, not a real path separator
+RewriteCond "%{THE_REQUEST}" "/horses%2F"
+RewriteRule "^/horses/ponies$" "/special-handler" [L]
+</highlight>
+
+ <p>After substitution, <module>mod_rewrite</module> re-encodes the
+ resulting URL-path for output. Several flags control this behavior:</p>
+
+ <ul>
+ <li><a href="flags.html#flag_b">[B]</a> — re-escape
+ backreferences so that special characters captured from the
+ decoded URL-path are not interpreted as delimiters in the
+ substitution.</li>
+
+ <li><a href="flags.html#flag_bnp">[BNP]</a> — when [B] is
+ active, encode spaces as <code>%20</code> rather than
+ <code>+</code> (appropriate for path components, not query
+ strings).</li>
+
+ <li><a href="flags.html#flag_ne">[NE]</a> — suppress the
+ default escaping of special characters in the substitution
+ result, allowing literal <code>#</code>, <code>?</code>, and
+ other characters to pass through unmodified on external
+ redirects.</li>
+ </ul>
+
+ <section id="allowencodedslashes">
+ <title>AllowEncodedSlashes</title>
+
+ <p>By default, Apache returns 404 for any URL containing an encoded
+ slash (<code>%2F</code>). The <directive
+ module="core">AllowEncodedSlashes</directive> directive controls
+ this behavior:</p>
+
+ <ul>
+ <li><code>Off</code> (default) — reject <code>%2F</code> with
+ 404.</li>
+ <li><code>On</code> — allow <code>%2F</code> and decode it to
+ <code>/</code> before passing to handlers.</li>
+ <li><code>NoDecode</code> — allow <code>%2F</code> but keep it
+ in encoded form, letting the backend application distinguish it
+ from a real path separator.</li>
+ </ul>
+
+ <p>When using the <a href="flags.html#flag_b">[B]</a> flag with
+ URLs that may contain encoded slashes, you typically need
+ <code>AllowEncodedSlashes NoDecode</code> to prevent Apache from
+ rejecting the re-encoded result.</p>
+
+ </section>
</section>
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.</p>
<p class="figure">
<seealso><a href="../mod/mod_rewrite.html">Module documentation</a></seealso>
<seealso><a href="intro.html">mod_rewrite introduction</a></seealso>
<seealso><a href="remapping.html">Redirection and remapping</a></seealso>
-<!--<seealso><a href="vhosts.html">Virtual hosts</a></seealso>-->
+<seealso><a href="htaccess.html">Per-directory Rewrites</a></seealso>
+<seealso><a href="flags.html">RewriteRule Flags</a></seealso>
<seealso><a href="rewritemap.html">RewriteMap</a></seealso>
-<seealso><a href="htaccess.html">Per-directory rewrites (.htaccess)</a></seealso>
<seealso><a href="avoid.html">When not to use mod_rewrite</a></seealso>
+<seealso><a href="tech.html">Technical details</a></seealso>
<section id="per-hostname">