Redirects are one of the most common and essential tasks you will handle with .htaccess. Whether you are moving a page to a new URL, restructuring your entire site, or forcing HTTPS, understanding how Apache redirects work is a fundamental skill for any web developer or system administrator.
This guide walks you through the Redirect and RedirectMatch directives, explains the difference between temporary and permanent redirects, covers regex-based matching, directory redirections, trailing slash handling, and the critical HTTP-to-HTTPS redirect pattern. By the end, you will have a solid understanding of how to implement redirects correctly and avoid common pitfalls.
How Redirects Work
When a browser or search engine requests a URL, the server can respond with a special HTTP status code that tells the client, "This resource is no longer here; go look at this other URL instead." This is a redirect.
Apache's mod_alias module provides the Redirect and RedirectMatch directives, which let you define these redirections directly inside your .htaccess file. When Apache processes a request and matches a redirect rule, it sends the appropriate HTTP status code along with the new URL in the Location header. The browser then automatically follows the new URL.
The basic flow looks like this:
- The client requests
/old-page. - Apache matches a redirect rule for
/old-page. - Apache responds with a 3xx status code and a
Location: /new-pageheader. - The client automatically requests
/new-page. - Apache serves the content at
/new-page.
The Redirect Directive
The Redirect directive is the simplest way to send a visitor from one URL to another. Its syntax is:
Redirect [status] URL-path URL-to-redirect-to- status (optional): The HTTP status code or keyword to return. Defaults to
temp(302). - URL-path: The original path to match (must start with
/). - URL-to-redirect-to: The destination URL, which can be a path on the same server or a full external URL.
Here is a basic example:
Redirect "/about" "/about-us"When a user visits https://example.com/about, they are automatically redirected to https://example.com/about-us with a 302 Temporary Redirect status.
You can also redirect to a completely different domain:
Redirect "/service" "https://newsite.example.com/service"Temporary Redirect (302)
A 302 Temporary Redirect tells browsers and search engines that the move is not permanent. The original URL should still be indexed by search engines, and browsers will not cache the redirect aggressively.
This is the default behavior when you do not specify a status:
Redirect "/one" "/two"
Redirect temp "/one" "/two"Use a 302 redirect when:
- You are temporarily moving a page during maintenance.
- You are running A/B tests.
- You want search engines to keep indexing the original URL.
Permanent Redirect (301)
A 301 Permanent Redirect tells browsers and search engines that the resource has moved forever. Search engines will transfer ranking signals (link equity) from the old URL to the new one, and browsers will cache this redirect.
Redirect permanent "/old-blog" "/blog"You can also use the numeric status code directly:
Redirect 301 "/old-blog" "/blog"Use a 301 redirect when:
- A page has been permanently moved to a new URL.
- You have restructured your site and old URLs will never return.
- You want to consolidate SEO value on the new URL.
All Available Status Options
Both Redirect and RedirectMatch support the following status keywords:
| Value | HTTP Code | Description |
|---|---|---|
temp |
302 | Temporary redirect. The resource is temporarily at a different URL. This is the default. |
permanent |
301 | Permanent redirect. The resource has moved permanently. |
seeother |
303 | See Other. The resource has been replaced and the client should use GET to retrieve the new URL. |
gone |
410 | Gone. The resource has been permanently removed. The destination URL should be omitted. |
Example of the gone status:
Redirect gone "/discontinued-product"RedirectMatch with Regex
While Redirect performs simple prefix matching, RedirectMatch gives you the power of regular expressions to match URL patterns. This is extremely useful when you need to redirect multiple URLs that follow a common pattern.
The syntax is:
RedirectMatch [status] regex URL-to-redirect-toBasic Example
Redirect a specific file regardless of whether it has a trailing slash:
RedirectMatch "^/oldfile\.html/?$" "https://example.com/newfile.php"Let's break down this regex:
^matches the beginning of the URL path./oldfile\.htmlmatches the literal string/oldfile.html(the dot is escaped)./?matches an optional trailing slash.$matches the end of the URL path.
This will match both /oldfile.html and /oldfile.html/.
Using Capture Groups
One of the most powerful features of RedirectMatch is the ability to capture parts of the matched URL and reuse them in the destination:
RedirectMatch "^/blog/[0-9]{4}/(.+)$" "/articles/$1"In this example:
[0-9]{4}matches any four-digit year.(.+)is a capture group that matches the rest of the URL (the post slug).$1in the destination refers to whatever was captured by the first group.
So a request to /blog/2023/my-great-post would redirect to /articles/my-great-post.
Multiple Capture Groups
You can use multiple capture groups:
RedirectMatch "^/products/([^/]+)/([^/]+)$" "/shop/$1/$2"$1will contain the category.$2will contain the item.
A request to /products/electronics/laptop redirects to /shop/electronics/laptop.
Common Mistake: Forgetting to Escape Dots
A dot (.) in regex matches any character, not just a literal dot. This can lead to unexpected behavior.
Wrong approach:
RedirectMatch "^/oldpage.html$" "/newpage.html"Correct approach:
RedirectMatch "^/oldpage\.html$" "/newpage.html"Redirecting Entire Directories
A common scenario is redirecting all URLs under one directory to another directory. There are multiple ways to handle this.
Using Redirect (Prefix Matching)
The Redirect directive already performs prefix matching by default. If the requested URL starts with the specified path, everything after it is appended to the destination:
Redirect permanent "/old-section/" "/new-section/"With this rule:
| Request URL | Redirects To |
|---|---|
/old-section/ |
/new-section/ |
/old-section/page1 |
/new-section/page1 |
/old-section/sub/page2 |
/new-section/sub/page2 |
This is very convenient and one of the reasons Redirect is often preferred over RedirectMatch for directory-level redirects.
Using RedirectMatch for More Control
If you need more control over how directory redirects work, use RedirectMatch:
RedirectMatch permanent "^/old-section/(.*)$" "/new-section/$1"This produces the same result as the Redirect example above, but gives you the flexibility to add conditions, such as excluding certain subpaths:
RedirectMatch permanent "^/old-section/(?!keep-this)(.*)$" "/new-section/$1"Common Mistake: Redirect Loops
Be careful when the source and destination overlap. This creates an infinite redirect loop:
Wrong approach:
Redirect "/page" "/page/"Since Redirect uses prefix matching, a request to /page/ still starts with /page, so it matches again, redirecting to /page//, and so on.
Correct approach:
Use RedirectMatch with an exact pattern:
RedirectMatch "^/page$" "/page/"Trailing Slash Handling
Inconsistent trailing slashes can cause duplicate content issues for SEO and confuse users. You often want to enforce a single canonical form, either always with or always without a trailing slash.
Adding a Trailing Slash
To ensure all directory-like URLs end with a trailing slash:
RedirectMatch permanent "^(/[^\.]+[^/])$" "$1/"This regex breaks down as:
^(/[^\.]+[^/])$matches any path that does not contain a dot (so it skips files likestyle.css) and does not already end with a slash.$1/appends a slash to the captured path.
| Request URL | Redirects To |
|---|---|
/about |
/about/ |
/blog/post |
/blog/post/ |
/style.css |
No redirect |
/about/ |
No redirect |
Removing a Trailing Slash
To remove trailing slashes instead:
RedirectMatch permanent "^(.+)/$" "$1"| Request URL | Redirects To |
|---|---|
/about/ |
/about |
/blog/post/ |
/blog/post |
/ |
No redirect |
Common Mistake: Trailing Slash Loop
Wrong approach:
RedirectMatch permanent "^(.+)/$" "$1"
RedirectMatch permanent "^(/[^\.]+[^/])$" "$1/"These two rules contradict each other and create an infinite loop. A request to /about/ triggers Rule 1 (redirecting to /about), which then triggers Rule 2 (redirecting back to /about/), and so on.
Correct approach: Pick one strategy and remove the conflicting rule.
HTTP to HTTPS Redirect
Forcing all traffic to use HTTPS is one of the most important redirects you can implement. It protects your users' data and is also a ranking factor for search engines.
While this is most commonly done with mod_rewrite, you can achieve it with mod_alias in some configurations. However, the mod_rewrite approach is the standard and most reliable method:
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{HTTPS} off
RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
</IfModule>Let's break down each line:
RewriteEngine Onenables the rewrite engine.RewriteCond %{HTTPS} offchecks if the request is NOT using HTTPS.RewriteRule ^(.*)$matches any URL path.https://%{HTTP_HOST}%{REQUEST_URI}constructs the same URL but withhttps://.[L,R=301]makes this a permanent redirect (R=301) and stops processing further rules (L).
Combining HTTPS with www Canonicalization
It is common to enforce both HTTPS and a canonical domain (with or without www) in a single set of rules:
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{HTTPS} off
RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
RewriteCond %{HTTP_HOST} ^www\.(.+)$ [NC]
RewriteRule ^(.*)$ https://%1%{REQUEST_URI} [L,R=301]
</IfModule>Or if you prefer to redirect non-www to www:
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{HTTPS} off
RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
RewriteCond %{HTTP_HOST} !^www\. [NC]
RewriteRule ^(.*)$ https://www.%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
</IfModule>Common Mistake: Redirect Loop with HTTPS Behind a Proxy
If your server is behind a load balancer or reverse proxy (such as Cloudflare or AWS ELB), the connection between the proxy and your server may be HTTP even when the user connects over HTTPS. This means %{HTTPS} will always be off, causing an infinite redirect loop.
Wrong approach (behind a proxy):
RewriteCond %{HTTPS} off
RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]Correct approach (behind a proxy):
Check the X-Forwarded-Proto header instead:
RewriteCond %{HTTP:X-Forwarded-Proto} !https
RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]Quick Reference
Here is a summary of the most common redirect patterns:
<IfModule mod_alias.c>
Redirect "/old" "/new"
Redirect permanent "/old-page" "/new-page"
Redirect 301 "/service" "https://other-site.com/service"
RedirectMatch permanent "^/blog/([0-9]{4})/(.+)$" "/articles/$2"
Redirect gone "/removed-page"
</IfModule>
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{HTTPS} off
RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
</IfModule>Understanding and correctly implementing redirects is a foundational skill that directly impacts user experience and SEO performance. Start with simple Redirect directives for straightforward URL changes, move to RedirectMatch when you need pattern matching, and use mod_rewrite for more complex scenarios like protocol enforcement. Always prefer 301 for permanent changes and 302 for temporary ones, and remember to test thoroughly to avoid redirect loops.