| <?xml version="1.0" encoding="UTF-8" ?> | |
| <!DOCTYPE manualpage SYSTEM "../style/manualpage.dtd"> | |
| <?xml-stylesheet type="text/xsl" href="../style/manual.en.xsl"?> | |
| <!-- $LastChangedRevision$ --> | |
| <!-- | |
| Licensed to the Apache Software Foundation (ASF) under one or more | |
| contributor license agreements. See the NOTICE file distributed with | |
| this work for additional information regarding copyright ownership. | |
| The ASF licenses this file to You under the Apache License, Version 2.0 | |
| (the "License"); you may not use this file except in compliance with | |
| the License. You may obtain a copy of the License at | |
| http://www.apache.org/licenses/LICENSE-2.0 | |
| Unless required by applicable law or agreed to in writing, software | |
| distributed under the License is distributed on an "AS IS" BASIS, | |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| See the License for the specific language governing permissions and | |
| limitations under the License. | |
| --> | |
| <manualpage metafile="htaccess.xml.meta"> | |
| <parentdocument href="./">Rewrite</parentdocument> | |
| <title>Per-directory Rewrites</title> | |
| <summary> | |
| <p>Using <module>mod_rewrite</module> in <code>.htaccess</code> files | |
| is one of the most common - and most confusing - | |
| <glossary ref="perdirectory">per-directory</glossary> configurations. | |
| This document explains the key differences between using rewrite | |
| rules in server configuration versus <code>.htaccess</code> files, | |
| and provides practical guidance for avoiding the most common pitfalls.</p> | |
| <p>For the low-level technical details of how <module>mod_rewrite</module> | |
| processes rules in per-directory context, see the | |
| <a href="tech.html#InternalAPI">Technical Details</a> document.</p> | |
| </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="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="flags.html">RewriteRule Flags</a></seealso> | |
| <seealso><a href="tech.html">Technical details</a></seealso> | |
| <seealso><a href="../mod/mod_rewrite.html#what_is_matched">What is matched?</a></seealso> | |
| <section id="prerequisites"><title>Prerequisites: AllowOverride</title> | |
| <p>Before <module>mod_rewrite</module> directives in a | |
| <code>.htaccess</code> file will be processed at all, the server | |
| configuration must permit them. This requires:</p> | |
| <highlight language="config"> | |
| <Directory "/var/www/htdocs"> | |
| AllowOverride FileInfo | |
| </Directory> | |
| </highlight> | |
| <p>Without at least <code>AllowOverride FileInfo</code> (or | |
| <code>AllowOverride All</code>), any <module>mod_rewrite</module> | |
| directives in <code>.htaccess</code> files are silently ignored. | |
| If your rules don't appear to be doing anything, this is the first | |
| thing to check.</p> | |
| <p>Additionally, either <code>Options FollowSymLinks</code> or | |
| <code>Options SymLinksIfOwnerMatch</code> must be enabled for the | |
| directory in question. Because a | |
| <directive module="mod_rewrite">RewriteRule</directive> can map a URL | |
| to an arbitrary filesystem path - functionally equivalent to a symbolic | |
| link - <module>mod_rewrite</module> refuses to operate in per-directory | |
| context unless one of these options is set. Without it, you will see | |
| the following error:</p> | |
| <example> | |
| AH00670: Options FollowSymLinks and SymLinksIfOwnerMatch are both off, | |
| so the RewriteRule directive is also forbidden due to its similar | |
| ability to circumvent directory restrictions | |
| </example> | |
| <p>This restriction applies to both <code>.htaccess</code> files and | |
| <directive module="core" type="section">Directory</directive> blocks.</p> | |
| </section> | |
| <section id="path-stripping"><title>What URL does the rule see?</title> | |
| <p>In server or virtualhost context, the | |
| <directive module="mod_rewrite">RewriteRule</directive> pattern is | |
| matched against the full URL-path, starting with a leading slash. | |
| In <code>.htaccess</code> context, the directory prefix is | |
| <strong>stripped</strong>.</p> | |
| <p>For example, if your <code>.htaccess</code> is in | |
| <code>/var/www/htdocs/app/</code> and a request comes in for | |
| <code>/app/products/widget</code>, the RewriteRule sees only | |
| <code>products/widget</code> - no leading slash, no | |
| <code>/app/</code> prefix.</p> | |
| <p class="figure"> | |
| <img src="../images/rewrite_path_stripping.png" | |
| alt="Flowchart showing the path stripping, substitution, | |
| and RewriteBase pipeline in per-directory context, with | |
| three outcomes: relative path (RewriteBase prepended), | |
| absolute path (used as-is), and absolute URI (external | |
| redirect)" /><br /> | |
| <dfn>Figure:</dfn> Per-directory path stripping and RewriteBase pipeline | |
| </p> | |
| <p>This means you must write your patterns differently depending on | |
| where the rule lives:</p> | |
| <table border="1"> | |
| <tr> | |
| <th>Location of rule</th> | |
| <th>Rule</th> | |
| </tr> | |
| <tr> | |
| <td>VirtualHost section</td> | |
| <td><code>RewriteRule "^/app/products/(.+)$" "/app/shop.php?item=$1"</code></td> | |
| </tr> | |
| <tr> | |
| <td>.htaccess in /var/www/htdocs/app/</td> | |
| <td><code>RewriteRule "^products/(.+)$" "shop.php?item=$1"</code></td> | |
| </tr> | |
| </table> | |
| <p>Note that the <code>.htaccess</code> version has no leading slash | |
| 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> | |
| <p>When <module>mod_rewrite</module> makes a substitution in | |
| <code>.htaccess</code> context, it needs to turn the relative result | |
| back into a full URL-path. The | |
| <directive module="mod_rewrite">RewriteBase</directive> directive tells | |
| it what prefix to prepend.</p> | |
| <p>By default, <directive module="mod_rewrite">RewriteBase</directive> | |
| is set to the physical directory path of the <code>.htaccess</code> | |
| file. In most cases, this does the right thing, and you don't need | |
| to set it explicitly. But there are situations where you do:</p> | |
| <ul> | |
| <li><strong>Alias or symlink:</strong> If the directory is reached via | |
| an <directive module="mod_alias">Alias</directive> or a symlink, the | |
| URL path and the filesystem path differ, and | |
| <directive module="mod_rewrite">RewriteBase</directive> must be set | |
| to the URL path.</li> | |
| <li><strong>Subdirectory applications:</strong> A common pattern for | |
| PHP frameworks is to place a <code>.htaccess</code> in a subdirectory | |
| (say, <code>/var/www/htdocs/myapp/</code>) and route all requests to | |
| a front controller:</li> | |
| </ul> | |
| <highlight language="config"> | |
| # In /var/www/htdocs/myapp/.htaccess | |
| RewriteEngine On | |
| RewriteBase "/myapp/" | |
| RewriteCond "%{REQUEST_FILENAME}" !-f | |
| RewriteCond "%{REQUEST_FILENAME}" !-d | |
| RewriteRule "^(.*)$" "index.php" [L] | |
| </highlight> | |
| <note>For this particular use case - routing all unmatched | |
| requests to a front controller - the | |
| <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 | |
| URL might resolve incorrectly, because <module>mod_rewrite</module> | |
| would prepend the filesystem path rather than the URL path.</p> | |
| <p>If you're using absolute URLs (starting with <code>/</code> or | |
| <code>http://</code>) in your substitutions, | |
| <directive module="mod_rewrite">RewriteBase</directive> has no effect | |
| - it only applies to relative substitutions.</p> | |
| </section> | |
| <section id="loops"><title>The [L] flag and looping</title> | |
| <p>In server context, the <code>[L]</code> flag means "stop processing | |
| the ruleset." In <code>.htaccess</code> context, it means something | |
| subtly different: "stop processing the ruleset <em>for this pass</em>." | |
| After the substitution is made, Apache re-processes the request from | |
| the top - including re-applying the <code>.htaccess</code> rules. | |
| This can lead to infinite loops.</p> | |
| <p class="figure"> | |
| <img src="../images/rewrite_l_flag_looping.png" | |
| alt="Flowchart showing how the [L] flag causes looping in | |
| per-directory context by triggering a subrequest that | |
| re-enters the ruleset" /><br /> | |
| <dfn>Figure:</dfn> Per-directory [L] flag looping behavior | |
| </p> | |
| <p>Consider this rule:</p> | |
| <highlight language="config"> | |
| # In .htaccess - this may loop! | |
| RewriteRule "^(.*)$" "/index.php?q=$1" [L] | |
| </highlight> | |
| <p>On the first pass, a request for <code>/hello</code> is rewritten to | |
| <code>/index.php?q=hello</code>. Then the request is re-processed, and | |
| now <code>index.php</code> matches <code>^(.*)$</code> again, rewriting | |
| to <code>/index.php?q=index.php</code>. This continues until Apache | |
| hits its internal redirect limit and returns a 500 error. You will see | |
| the following in the error log:</p> | |
| <example> | |
| AH00124: Request exceeded the limit of 10 internal redirects due to | |
| probable configuration error. Use 'LimitInternalRecursion' to increase | |
| the limit if necessary. Use 'LogLevel debug' to get a backtrace. | |
| </example> | |
| <p>There are several ways to break the loop:</p> | |
| <p><strong>Option 1: Use the [END] flag</strong> (recommended)</p> | |
| <highlight language="config"> | |
| RewriteRule "^(.*)$" "/index.php?q=$1" [END] | |
| </highlight> | |
| <p>The <code>[END]</code> flag (available since Apache 2.3.9) stops | |
| <em>all</em> further rewrite processing, including subsequent passes. | |
| It is the cleanest way to prevent loops.</p> | |
| <p><strong>Option 2: Add a condition to skip already-rewritten URLs</strong></p> | |
| <highlight language="config"> | |
| RewriteCond "%{REQUEST_FILENAME}" !-f | |
| RewriteCond "%{REQUEST_FILENAME}" !-d | |
| RewriteRule "^(.*)$" "/index.php?q=$1" [L] | |
| </highlight> | |
| <p>Since <code>index.php</code> exists as a file, the | |
| <code>!-f</code> condition causes the rule to be skipped on the second | |
| pass.</p> | |
| <p><strong>Option 3: Check THE_REQUEST</strong></p> | |
| <highlight language="config"> | |
| RewriteCond "%{THE_REQUEST}" "!index\.php" | |
| RewriteRule "^(.*)$" "/index.php?q=$1" [L] | |
| </highlight> | |
| <p>The <code>%{THE_REQUEST}</code> variable contains the original | |
| request line as sent by the client, which is not modified by | |
| <module>mod_rewrite</module>. Checking it prevents the rule from | |
| matching rewritten URLs.</p> | |
| </section> | |
| <section id="rewritemap-restriction"><title>RewriteMap cannot be | |
| declared in .htaccess</title> | |
| <p>The <directive module="mod_rewrite">RewriteMap</directive> directive | |
| can only be declared in server or virtualhost context - not in | |
| <code>.htaccess</code> files or | |
| <directive module="core" type="section">Directory</directive> blocks. | |
| However, once a map is declared in the server configuration, you | |
| <em>can</em> use it from a <code>.htaccess</code> file:</p> | |
| <highlight language="config"> | |
| # In httpd.conf or a VirtualHost | |
| RewriteMap product2id "txt:/etc/apache2/productmap.txt" | |
| </highlight> | |
| <highlight language="config"> | |
| # In .htaccess - using the map declared above | |
| RewriteEngine On | |
| RewriteRule "^product/(.+)$" "/prods.php?id=${product2id:$1|NOTFOUND}" [PT] | |
| </highlight> | |
| <p>This restriction exists because <code>.htaccess</code> files are | |
| parsed on every request, and map initialization (especially for | |
| <code>dbm:</code>, <code>dbd:</code>, and <code>prg:</code> map types) | |
| would be prohibitively expensive to repeat each time.</p> | |
| </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 | |
| inherited</strong> by subdirectories. If you define rules in | |
| <code>/var/www/htdocs/.htaccess</code>, they apply to that directory | |
| only. A <code>.htaccess</code> file in a subdirectory starts with | |
| an empty ruleset, unless you explicitly enable inheritance.</p> | |
| <p>The <directive module="mod_rewrite">RewriteOptions</directive> | |
| directive controls this behavior:</p> | |
| <dl> | |
| <dt><code>RewriteOptions Inherit</code></dt> | |
| <dd>Rules from the parent context are appended to the current ruleset. | |
| The child's rules are processed first, then the parent's. Use this | |
| when a subdirectory needs to add its own rules while keeping the | |
| parent's rules active.</dd> | |
| <dt><code>RewriteOptions InheritBefore</code></dt> | |
| <dd>Like <code>Inherit</code>, but the parent's rules are processed | |
| <em>before</em> the child's. This is useful when the parent defines | |
| a front-controller pattern and the child needs to add exceptions. | |
| Available since Apache 2.4.8.</dd> | |
| <dt><code>RewriteOptions InheritDown</code></dt> | |
| <dd>Set this in the parent context to force all child contexts to | |
| inherit the parent's rules, without requiring each child to specify | |
| <code>Inherit</code>. Available since Apache 2.4.8.</dd> | |
| <dt><code>RewriteOptions InheritDownBefore</code></dt> | |
| <dd>Like <code>InheritDown</code>, but forces the parent's rules to | |
| run before the child's. Available since Apache 2.4.8.</dd> | |
| <dt><code>RewriteOptions IgnoreInherit</code></dt> | |
| <dd>Set this in a child context to opt out of inheritance that was | |
| forced by a parent's <code>InheritDown</code>. | |
| Available since Apache 2.4.8.</dd> | |
| <dt><code>RewriteOptions MergeBase</code></dt> | |
| <dd>When inheritance is enabled, the <code>RewriteBase</code> from | |
| each context is used for rules defined in that context, rather than | |
| applying the child's <code>RewriteBase</code> to all inherited rules. | |
| Available since Apache 2.4.26.</dd> | |
| </dl> | |
| </section> | |
| <section id="debugging"><title>Debugging .htaccess rewrite rules</title> | |
| <p>When <code>.htaccess</code> rules are not doing what you expect, | |
| the rewrite log is your most important tool. Enable it at the | |
| appropriate trace level:</p> | |
| <highlight language="config"> | |
| LogLevel alert rewrite:trace3 | |
| </highlight> | |
| <p>This produces detailed output in the error log showing exactly | |
| how each rule is processed - what pattern was matched against, | |
| whether conditions succeeded or failed, and what substitution was | |
| made. The per-directory context and path stripping behavior will be | |
| visible in these log entries.</p> | |
| <note type="warning">Do not leave trace-level logging enabled in | |
| production. It generates a large volume of output and will affect | |
| performance.</note> | |
| </section> | |
| <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> | |
| </manualpage> |