Creating rules in .htaccess
| Redirect: simple moves | |
| RedirectMatch: patterns | |
| RewriteRule basics | |
| Flags that matter | |
| Order of rules | |
| Testing rules | |
| Common pitfalls |
Redirect: simple moves
One page moved to a new address. mod_alias handles it in one line - source first, target second:
Redirect 301 /ru/qa/robot/newline.php /ru/qa/robot/basics/newline.php
Use 301 for permanent moves (link equity passes, browsers cache it) and 302 while experimenting. A cached wrong 301 is painful: browsers remember it long after you fix the file, so test with 302 and flip to 301 when sure.
RedirectMatch: patterns
Plain Redirect matches a URL prefix, which glues the remainder onto the target. To retire a whole tree into one file, anchor a regular expression instead:
RedirectMatch 301 ^/edu/suomen/.*$ /edu/index.php
Without the regex, /edu/suomen/x.php would land on /edu/index.phpx.php. Exact single-address matches work the same way:
RedirectMatch 301 ^/robot/?$ /qa/robot/
The /?$ anchor means subpaths like /robot/basics/ are untouched - only the bare directory redirects.
RewriteRule basics
When the target depends on part of the source, mod_rewrite captures it with parentheses and replays it as $1, $2:
RewriteEngine On RewriteRule ^blog/([0-9]{4})/([a-z-]+)\.php$ /archive/$1/$2/ [R=301,L]
RewriteRule sees the path without the leading slash and without the query string. To keep query parameters, add the QSA flag.
Flags that matter
- R=301 / R=302 - external redirect and its status. Without R the rewrite is internal: the URL in the browser never changes.
- L (last) - stop processing further rules on a match. Almost every redirect rule wants it.
- NC (nocase) - case-insensitive match, handy for legacy mixed-case URLs.
- QSA (qsappend) - append the original query string to the target instead of dropping it.
- NE (noescape) - do not percent-encode the target, needed when it contains # anchors.
Order of rules
Apache tries rules top to bottom, and a prefix-hungry rule shadows everything below it. Specific rules go first, catch-alls last:
Redirect 301 /pc/LaTeX.php /latex/man/ RedirectMatch 301 ^/pc/LaTeX/.*$ /latex/index.php
Reversed, the catch-all would swallow /pc/LaTeX.php is not matched by ^/pc/LaTeX/.*$ - but a plain Redirect 301 /pc/LaTeX/ prefix above the specific line would shadow it. Comment the ordering constraint in the file, or the next editor will reorder it back.
Testing rules
Never test in a browser first - the 301 cache lies. Ask for headers:
curl -s -o /dev/null -w "%{http_code} %{redirect_url}\n" https://aredel.com/robot/
301 https://aredel.com/qa/robot/
Check the chain, not just the first hop: two redirects in a row double the latency and leak ranking. One hop from old URL to final page is the goal.
Common pitfalls
- Prefix gluing: Redirect 301 /edu/suomen/ to a file appends the remainder. Use RedirectMatch with .*$ when retiring a tree.
- Redirect targets must exist. Audit them against the document root - a redirect to a missing page trades a 404 for a slower 404.
- Redirect fires even when a real file sits at the source path, so a stale rule can hijack a live page. Retire rules whose source came back to life.
- Trailing slashes: ^/robot/?$ covers both spellings; without ? one of them 404s.
- Chained redirects (/linux/virtualbox.php to /virtualbox/ to /dvps/virtualbox/) work but cost a round trip each - collapse to one hop when editing anyway.
Keep the same discipline as with code: one rule per move, a comment above non-obvious ones, and a periodic audit of every target.
Article author: Andrei Olegovich