Apache tool
Paste your rules, give them a URL, and watch which line wins. Every condition is evaluated in front of you, so a rule that quietly never matches stops being a mystery.
This is the one that costs people an afternoon. Apache strips the directory prefix from the
path before a per-directory ruleset ever sees it. A .htaccess in the document root
matches against blog/hello, not /blog/hello, so ^/blog/
matches nothing at all. Move the same rules into httpd.conf and the slash becomes
mandatory, which is why copied snippets so often behave differently from where they came from.
In a .htaccess, [L] ends the current sweep through the file. Apache
then restarts the whole ruleset with the rewritten URL. A rule that rewrites to something it also
matches will run until Apache hits its internal redirect limit and answers 500. Guard the rule with
RewriteCond %{REQUEST_FILENAME} !-f, or use [END], which stops the
restart as well.
A RewriteCond is not a filter that runs first. Apache tests the
RewriteRule pattern, and only if it matches does it work back through the conditions
stacked above it. That ordering matters for %1 backreferences, which come from the
last condition that matched, while $1 comes from the rule pattern.
Stack three conditions and all three must hold. [OR] binds a condition to the one
after it, so a run of conditions ending in a plain one forms a single OR group, and groups are
combined with AND. Mixing the two without thinking about that grouping produces rules that fire on
traffic you meant to exclude.
| Flag | What it does |
|---|---|
| [L] | Stop this pass. Apache restarts the file with the new URL. |
| [END] | Stop rewriting entirely, restart included. Apache 2.4 and later. |
| [R=301] | Send the browser a real redirect. The address bar changes. Without a code you get 302. |
| [NC] | Match without regard to case. |
| [QSA] | Keep the original query string and append the new one. |
| [QSD] | Throw the original query string away. |
| [NE] | Do not escape characters in the substitution. Needed for anchors and pre-encoded paths. |
| [F] | 403 Forbidden. Implies [L]. |
| [G] | 410 Gone. Implies [L]. |
| [C] | Chain: if the next rule fails, the rest of the chain is skipped. |
| [S=n] | Skip the next n rules when this one matches. |
| [E=VAR:val] | Set an environment variable, readable later as %{ENV:VAR}. |
| [P] | Proxy the request through mod_proxy instead of redirecting. |
RewriteEngine On
RewriteCond %{HTTPS} off
RewriteRule ^(.*)$ https://%{HTTP_HOST}/$1 [R=301,L]
Behind Cloudflare or any other proxy that terminates TLS, %{HTTPS} reads
off even on an https request, and this rule loops. Test
%{HTTP:X-Forwarded-Proto} instead.
RewriteEngine On
RewriteCond %{HTTP_HOST} ^www\.(.+)$ [NC]
RewriteRule ^(.*)$ https://%1/$1 [R=301,L]
The %1 is the hostname captured by the condition. Getting $1 and
%1 the wrong way round here sends every request to a domain that does not exist.
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ /index.php?page=$1 [QSA,L]
Drop either condition and the rewritten /index.php matches the rule again on the
restart. List /index.php in the paths box above to see the guard doing its job.
RewriteRule ^pricing/?$ /pricing.php [L,QSA]
The /? accepts the trailing slash without a second rule and without a redirect.
It parses the rules and walks them exactly as Apache would, up to the ten internal redirects Apache allows before it gives up. Nothing is fetched from your site and nothing is written to disk. Your rules are not stored or logged, which is also why there is no history to come back to.
Filesystem tests are answered from the paths you list, not from any real server, so
-f and -d are only as accurate as that list. -U and
-F fire real subrequests on a live server and are treated as false here.
RewriteMap is not simulated. Directives that shape headers, access or caching are
listed in the trace and then left alone, since they do not decide which URL gets served.
Longer walkthrough of the syntax, with the parts of .htaccess that have nothing to
do with rewriting: the htaccess tutorial.