Redirects in .htaccess are one of the most common Apache web server configuration tasks. Changing a domain, switching to HTTPS, changing the URL structure, consolidating www and non-www—all these cases require correctly configured redirect rules. This article provides ready-to-use examples for common and less common situations. Before applying the rules, we recommend checking redirect chains with the reChecker redirect checker.
Basic redirect directives
Apache supports several ways to configure redirects. The right choice depends on how complex the condition is and whether regular expressions are needed.
Redirect vs RewriteRule
| Directive | Module | Syntax | Regular expressions |
|---|---|---|---|
Redirect | mod_alias | Simple path | No |
RedirectMatch | mod_alias | Regex | Yes |
RewriteRule | mod_rewrite | Regex | Yes |
The Redirect directive is easier to use, but does not support conditions. Use mod_rewrite for more complex logic. Generate ready-to-use rules with the .htaccess Generator.
Simple Redirect
# 301 — постоянный редирект
Redirect 301 /old-page.html /new-page.html
Redirect 301 /category/old-name /category/new-name
# 302 — временный редирект
Redirect 302 /maintenance /coming-soon
# 307 — временный с сохранением метода (POST остаётся POST)
Redirect 307 /api/submit /api/v2/submit
# 308 — постоянный с сохранением метода
Redirect 308 /old-api /new-apiImportant: when using Redirect, the path in the first argument is interpreted as a prefix. The request /old-page.html?id=1 will be redirected to /new-page.html?id=1—the query string is preserved automatically.
RedirectMatch with regular expressions
Use RedirectMatch when you need patterns. The reChecker Regex Tester. is useful for debugging regexes.
# Редирект всех .html на версию без расширения
RedirectMatch 301 ^/(.+)\.html$ /$1
# Редирект старых ID страниц
RedirectMatch 301 ^/product/([0-9]+)\.php$ /products/$1
# Редирект датированных URL
RedirectMatch 301 ^/blog/([0-9]{4})/([0-9]{2})/(.+)\.html$ /articles/$3mod_rewrite: full control
mod_rewrite offers maximum flexibility: conditions, captured groups, and environment variable checks.
Enabling the engine
RewriteEngine On
RewriteBase /RewriteBase sets the base path for substitutions. At the site root, this is usually /.
RewriteRule flags
| Flag | Purpose |
|---|---|
R=301 | Response code 301 |
R=302 | Response code 302 |
L | Last—stop processing rules |
NC | No Case—ignore case |
QSA | Query String Append—append the original query string |
NE | No Escape—do not escape special characters in the substitution |
OR | Or—combine conditions (RewriteCond) |
Basic RewriteRule examples
RewriteEngine On
# Одна страница на другую
RewriteRule ^old-page$ /new-page [R=301,L]
# С сохранением query string
RewriteRule ^search\.php$ /search [R=301,L,QSA]
# Игнорирование регистра
RewriteRule ^About$ /about [R=301,L,NC]
# Захват группы и подстановка
RewriteRule ^blog/post-([0-9]+)\.html$ /articles/$1 [R=301,L]HTTP → HTTPS redirect
A required step after installing SSL. All HTTP traffic should be redirected to HTTPS.
RewriteEngine On
RewriteCond %{HTTPS} off
RewriteRule ^(.*)$ https://%{HTTP_HOST}/$1 [R=301,L]Variant with the domain specified explicitly:
RewriteCond %{HTTPS} off
RewriteRule ^(.*)$ https://example.com/$1 [R=301,L]Another option using mod_rewrite while preserving the original path:
RewriteEngine On
RewriteCond %{HTTPS} off
RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]Consolidating www and non-www
Two versions of a site create duplicate content. Choose one canonical version and configure a redirect.
From www to non-www
RewriteEngine On
RewriteCond %{HTTP_HOST} ^www\.(.*)$ [NC]
RewriteRule ^(.*)$ https://%1/$1 [R=301,L]From non-www to www
RewriteEngine On
RewriteCond %{HTTP_HOST} !^www\. [NC]
RewriteCond %{HTTP_HOST} ^(.+)$ [NC]
RewriteRule ^(.*)$ https://www.%1/$1 [R=301,L]Combined: HTTP → HTTPS + www
RewriteEngine On
# Сначала на HTTPS
RewriteCond %{HTTPS} off
RewriteRule ^(.*)$ https://%{HTTP_HOST}/$1 [R=301,L]
# Затем на www
RewriteCond %{HTTP_HOST} !^www\. [NC]
RewriteCond %{HTTP_HOST} ^(.+)$ [NC]
RewriteRule ^(.*)$ https://www.%1/$1 [R=301,L]For more on choosing a canonical domain and SEO considerations, see the guide to redirect optimization.
Trailing slash: with or without a slash
Consistent URLs affect indexing. Choose one format.
Add a trailing slash
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_URI} !(.*)/$
RewriteRule ^(.*)$ /$1/ [R=301,L]Remove a trailing slash
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)/$ /$1 [R=301,L]The !-d condition prevents redirects for actual directories (otherwise /blog/ will no longer open the folder).
Changing a domain
When moving a site to a new domain, every old URL should lead to its corresponding page on the new domain.
RewriteEngine On
RewriteCond %{HTTP_HOST} ^old-domain\.com$ [OR]
RewriteCond %{HTTP_HOST} ^www\.old-domain\.com$
RewriteRule ^(.*)$ https://new-domain.com/$1 [R=301,L]Preserving the path structure:
RewriteRule ^(.*)$ https://new-domain.com/$1 [R=301,L,QSA]QSA preserves parameters: old-domain.com/page?utm_source=google → new-domain.com/page?utm_source=google.
Redirects with regular expressions
Remove the .html extension
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.+)\.html$ /$1 [R=301,L]Add .html (reverse redirect)
RewriteCond %{REQUEST_FILENAME}.html -f
RewriteRule ^(.+)$ $1.html [R=301,L]Redirect old IDs to a new format
# /item.php?id=12345 → /items/12345
RewriteCond %{QUERY_STRING} ^id=([0-9]+)$
RewriteRule ^item\.php$ /items/%1? [R=301,L]The ? character at the end of the substitution removes the original query string. Without it, the result will be /items/12345?id=12345.
Redirect based on the language in the URL
# /en/about → /about (удаление префикса языка)
RewriteRule ^en/(.*)$ /$1 [R=301,L]
# Или наоборот: /about → /ru/about
RewriteCond %{HTTP:Accept-Language} ^ru [NC]
RewriteRule ^(.*)$ /ru/$1 [R=302,L]Redirects with conditions
Redirect only for a specific User-Agent
RewriteCond %{HTTP_USER_AGENT} ^Googlebot [NC]
RewriteRule ^old-sitemap\.xml$ /new-sitemap.xml [R=301,L]Redirect by IP (for example, for beta testers)
RewriteCond %{REMOTE_ADDR} ^192\.168\.1\.(100|101|102)$
RewriteRule ^(.*)$ /beta/$1 [R=302,L]Redirect mobile users to a separate subdomain
RewriteCond %{HTTP_USER_AGENT} (android|blackberry|iphone|ipad|ipod) [NC]
RewriteCond %{HTTP_HOST} !^m\.example\.com$
RewriteRule ^(.*)$ https://m.example.com/$1 [R=302,L]Redirecting entire sections
Changing the directory structure
# /shop/category/old-name/ → /catalog/new-name/
RewriteRule ^shop/category/old-name/(.*)$ /catalog/new-name/$1 [R=301,L]
# Все страницы раздела
RewriteRule ^old-section/(.*)$ /new-section/$1 [R=301,L]Redirect with changed parameters
# /page?sort=old → /page?order=new
RewriteCond %{QUERY_STRING} sort=old
RewriteRule ^page$ /page?order=new [R=301,L,QSD]QSD (Query String Discard) removes the original query string before adding a new one.
Redirects for errors and special cases
Redirect 404 to the home page
ErrorDocument 404 /index.htmlOr to a custom page:
ErrorDocument 404 https://example.com/404
ErrorDocument 500 https://example.com/500Redirect while preserving the path (for an SPA)
In single-page applications, all unknown paths often lead to index.html:
RewriteEngine On
RewriteBase /
RewriteRule ^index\.html$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.html [L]Here, [L] without R is an internal rewrite, with no HTTP redirect.
Rule order and performance
Rules are processed from top to bottom. Recommendations:
- Put specific rules first—individual pages and exact paths.
- Then add general rules—domain, HTTPS, www, trailing slash.
- Use
L—stop processing after a rule matches. - Avoid redirect loops—check conditions (for example,
RewriteCond %{HTTPS} offbefore redirecting to HTTPS).
A typical structure:
RewriteEngine On
RewriteBase /
# 1. Специфичные страницы
RewriteRule ^old-contact$ /contact [R=301,L]
RewriteRule ^legacy-page\.php$ /new-page [R=301,L,QSA]
# 2. HTTP → HTTPS
RewriteCond %{HTTPS} off
RewriteRule ^(.*)$ https://%{HTTP_HOST}/$1 [R=301,L]
# 3. www / non-www
RewriteCond %{HTTP_HOST} !^www\. [NC]
RewriteRule ^(.*)$ https://www.%{HTTP_HOST}/$1 [R=301,L]Testing and debugging
After configuring redirects, check:
- Redirect chains—there should be no double redirects or loops. Use the reChecker redirect checker.
- Response code—301 for permanent redirects, 302 for temporary ones.
- Parameter preservation—add
QSAif needed. - Caching—browsers cache 301; test in incognito mode or with the cache cleared.
Redirect based on response code
Sometimes you need to redirect only when a specific status is returned. Using ErrorDocument:
ErrorDocument 404 https://example.com/404
ErrorDocument 403 https://example.com/access-denied
ErrorDocument 500 https://example.com/errorRedirect 404 to a relevant page (for example, a search using the slug):
ErrorDocument 404 /index.php?page=404Redirects that account for the query string
By default, RewriteRule does not pass the query string. Add QSA (Query String Append):
RewriteRule ^old-path$ /new-path [R=301,L,QSA]The request /old-path?utm_source=google will become /new-path?utm_source=google.
To remove the query string when redirecting, add ? to the end of the substitution:
RewriteRule ^old-path$ /new-path? [R=301,L]Redirecting mobile devices
A separate subdomain for the mobile version:
RewriteCond %{HTTP_USER_AGENT} (android|blackberry|iphone|ipad|ipod|mobile) [NC]
RewriteCond %{HTTP_HOST} !^m\.example\.com$
RewriteRule ^(.*)$ https://m.example.com/$1 [R=302,L]302 is temporary, so search engines do not transfer all ranking weight to the m-version. For mobile-first, it is better not to use a separate subdomain.
Redirect by geolocation
Requires mod_geoip or mod_geoip2:
RewriteCond %{ENV:GEOIP_COUNTRY_CODE} ^RU$
RewriteRule ^(.*)$ https://ru.example.com/$1 [R=302,L]Common mistakes
- Redirect loop—a rule redirects to a URL that matches the same rule again. Check conditions (HTTPS, www).
- Lost query string—add
QSAor pass$QUERY_STRINGexplicitly. - Double redirect—HTTP→HTTPS and www→non-www are configured in the wrong order. Apply HTTPS first, then the canonical domain.
- 500 error—syntax in
RewriteRule, an incorrect file path, or missingRewriteEngine On.
For a complete overview of .htaccess features, see the .htaccess guide.
How to check an 301 redirect before and after a change
First, save the original URL, its response, and the redirect chain using the redirect checker. For a list of old pages, use the bulk server response checker. Choose one destination URL for each page; sending all old URLs to the home page does not confirm that their content has been preserved.
An illustrative example, not a measurement of a real site:
| URL | Before the change | Expected result afterward |
|---|---|---|
https://example.com/old-page | 404, no content | 301 to /new-page, then 200 with the required content |
http://example.com/new-page | 200 on the HTTP version | One redirect to the canonical HTTPS address |
https://example.com/new-page | 200 | 200 with no loop or unnecessary redirect |
301 means a permanent move; 302 means a temporary one. If the application accepts POST requests and preserving the request method matters, separately check the use of 307/308 and the client's behavior. Do not transfer rules from .htaccess to Nginx: it is a different server with different syntax.
After uploading the rules, send a GET request to the original URL and record the status code at each step, Location, the final URL, and the content of the destination page. Then open the address in a browser and check the required action. An 200 response with a login form or an application error does not complete the check. Check the query parameters, slash, HTTPS, and www to ensure that a neighboring rule does not cause another redirect.
Record the time and the actual result, not just the expected behavior. If an error occurs, restore the previous configuration and fix the specific rule. The syntax for external redirects is covered in the Apache documentation.