.htaccess redirects on Apache
Everything you need to redirect pages and whole domains with .htaccess on Apache: mod_alias vs. mod_rewrite, copy-ready rules for the common cases, and how to avoid loops and chains.
Two modules, two syntaxes
Apache offers two ways to redirect in a .htaccess file. Both work, but they behave differently, so it helps to know which one you are using:
| Module | Directives | Good for |
|---|---|---|
| mod_alias | Redirect, RedirectMatch, RedirectPermanent | Simple path-to-URL redirects, readable one-liners |
| mod_rewrite | RewriteEngine, RewriteCond, RewriteRule | Conditions on host, protocol, query string or files; HTTPS and www redirects |
Two practical notes before you start. First, .htaccess only works if the server allows it: redirect and rewrite directives need AllowOverride FileInfo (or All) for the directory, and mod_rewrite additionally needs Options FollowSymLinks or SymLinksIfOwnerMatch. On shared hosting this is usually already set. Second, if you have access to the virtual host configuration, the Apache documentation recommends putting rules there instead, because .htaccess files are read on every request.
Redirect a single page
With mod_alias, a single line is enough. The path is the URL path starting with a slash, the target should be a full URL:
Redirect 301 /old-page.html https://example.com/new-page/
If you omit the status code, Redirect sends a 302, which is a temporary redirect. For moved content you almost always want 301 (or permanent). If you are unsure which code fits, read 301 vs. 302.
Important: Redirect matches prefixes. Redirect 301 /shop https://example.com/store also redirects /shop/shoes to /store/shoes and even /shopping to /storeping. That is handy for moving a folder, but surprising for single pages. If you need an exact match, use RedirectMatch with anchors:
RedirectMatch 301 ^/shop$ https://example.com/store/
The same with mod_rewrite. Note that in .htaccess the pattern is matched against the path without the leading slash:
RewriteEngine On
RewriteRule ^old-page\.html$ https://example.com/new-page/ [R=301,L]
Redirect a folder or pattern
RedirectMatch takes a regular expression and lets you reuse captured parts with $1, $2 and so on:
RedirectMatch 301 ^/blog/(\d{4})/(.*)$ https://example.com/articles/$2
The mod_rewrite equivalent:
RewriteEngine On
RewriteRule ^blog/\d{4}/(.*)$ https://example.com/articles/$1 [R=301,L]
Always anchor your patterns with ^ and $. An unanchored RedirectMatch 301 /old https://example.com/ matches every URL that contains /old anywhere.
Redirect a whole domain
When you move to a new domain, every old URL should point to the same path on the new domain. If the old domain has its own document root, this one line does it, because Redirect appends the rest of the path:
Redirect 301 / https://example.com/
If old and new domain share the same document root, Redirect would loop. Use a host condition instead:
RewriteEngine On
RewriteCond %{HTTP_HOST} ^(www\.)?old-domain\.com$ [NC]
RewriteRule ^ https://example.com%{REQUEST_URI} [R=301,L,NE]
NE (no escape) keeps already encoded characters in the path from being encoded twice. For the full process around a move, see the domain migration checklist.
HTTP to HTTPS
RewriteEngine On
RewriteCond %{HTTPS} off
RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L,NE]
Behind a load balancer, CDN or reverse proxy that terminates TLS, Apache sees every request as plain HTTP and this rule loops. In that case check the header the proxy sets instead:
RewriteEngine On
RewriteCond %{HTTP:X-Forwarded-Proto} !https
RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L,NE]
More background, including HSTS, is in the HTTP to HTTPS guide.
www and non-www in a single hop
A common mistake is one rule for HTTPS and a second one for www. Then http://www.example.com/ goes through two redirects. This block handles both cases and always sends visitors directly to https://example.com:
RewriteEngine On
RewriteCond %{HTTPS} off [OR]
RewriteCond %{HTTP_HOST} ^www\. [NC]
RewriteCond %{HTTP_HOST} ^(?:www\.)?(.+)$ [NC]
RewriteRule ^ https://%1%{REQUEST_URI} [R=301,L,NE]
The first two conditions are joined with OR, the third one always applies and captures the host name without www. into %1. If you prefer the www version, use this instead:
RewriteEngine On
RewriteCond %{HTTPS} off [OR]
RewriteCond %{HTTP_HOST} !^www\. [NC]
RewriteCond %{HTTP_HOST} ^(?:www\.)?(.+)$ [NC]
RewriteRule ^ https://www.%1%{REQUEST_URI} [R=301,L,NE]
The www redirect guide explains which version to pick.
Trailing slash
Add a trailing slash to URLs that are not files:
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_URI} !/$
RewriteRule ^(.*)$ https://example.com/$1/ [R=301,L]
Or remove it, except for real directories (Apache's mod_dir adds the slash to directories on its own, so removing it there would loop):
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.+)/$ https://example.com/$1 [R=301,L]
Query strings
By default, both modules keep the query string: /old?ref=mail ends up at /new?ref=mail. With mod_rewrite you control this with flags:
QSDdiscards the query string (Apache 2.4 and later).QSAappends the original query string when the target contains its own?.
RewriteRule never sees the query string in its pattern. To redirect based on a parameter, use a RewriteCond:
RewriteEngine On
RewriteCond %{QUERY_STRING} (^|&)id=42(&|$)
RewriteRule ^product\.php$ https://example.com/products/blue-widget/ [R=301,L,QSD]
Order of rules and avoiding loops
- Specific before general. Rules are processed top to bottom. Put single-page redirects above catch-all rules like the domain or HTTPS redirect, otherwise the general rule fires first and you get a chain.
- Always use
LwithR. WithoutL, Apache keeps processing following rules and may modify the target. - Do not mix mod_alias and mod_rewrite. The two modules are processed independently, so the order in the file does not decide which one wins. Pick one per file, ideally mod_rewrite once you need any condition.
- Make sure the target does not match the rule.
RedirectMatch 301 ^/(.*)$ https://example.com/new/$1on the same host redirects/new/again and again. Exclude the target with a condition or a more precise pattern. - Watch for other layers. CMS plugins, your hosting panel or a CDN can redirect too. Two layers redirecting the same URL is the most common source of chains and "too many redirects" errors. The guide on redirect chains shows how to find them.
Testing your rules
Browsers cache 301 redirects aggressively, sometimes indefinitely. While you are still experimenting, use R=302 and switch to R=301 once everything works, or test in a private window. Better still, test with a tool that does not cache at all: paste the old URL into the redirect checker and check that you get exactly one hop, the right status code and the right target. Test the variants too: http://, www., with and without trailing slash, with a query string.
If you get a 500 Internal Server Error after saving, there is a syntax error in the file or a module is not enabled. Apache's error log names the line. For many redirects at once, the bulk redirect checker saves time.
Don't want to write rules by hand? The .htaccess redirect generator builds them for you from a list of old and new URLs.
Frequently asked questions
- Where do I put the .htaccess file?
In the document root of your website, usually the folder that contains
index.htmlorindex.php. Rules in a subfolder's.htaccessonly apply to requests for that folder, and paths inRewriteRulepatterns are then relative to it.- Should I use Redirect or RewriteRule?
For a handful of simple path redirects,
RedirectandRedirectMatchare easier to read. As soon as you need a condition (host name, HTTPS, query string, file exists), useRewriteRule. Don't mix both in the same file, because their processing order does not follow the order in the file.- Why does my .htaccess redirect have no effect?
The most common causes:
AllowOverrideis set toNone, so Apache ignores the file; mod_rewrite is not enabled; your browser shows a cached old redirect; or a rule further up already matched. Check with the redirect checker, which never uses a cache, and look at Apache's error log.- Does .htaccess work on nginx?
No. nginx does not read
.htaccessfiles at all. You need to translate the rules into the nginx configuration, see the nginx redirect guide.
Related guides
-
nginx redirects
Copy-ready nginx configuration for 301 redirects: single pages, patterns, whole domains, HTTPS and www in a single hop, and hundreds of URLs with map.
-
Redirects on Cloudflare
Cloudflare can answer redirects at the edge before a request ever reaches your server. Here is how Single Redirects, Bulk Redirects and Always Use HTTPS work, and how to keep them from fighting with your origin.
-
Redirects in WordPress
WordPress gives you several ways to redirect a URL: a plugin, a rule in .htaccess or a few lines of PHP. This guide shows when to use which, and how to avoid the typical pitfalls with caching and HTTPS.
-
Redirects in Next.js
Next.js has four places where you can redirect: the config file, middleware, server code in the App Router and the trailingSlash option. Each one sends different status codes by default, so it pays to know which is which.
-
Redirects on Vercel and Netlify
On Vercel and Netlify you don't touch a web server config. Redirects live in a file in your repository or in the dashboard, and the platform's edge network sends them. Here is how both work and where they differ.
-
Redirects on Microsoft IIS
IIS gives you two ways to redirect: the built-in HTTP Redirect feature and the URL Rewrite module. Here is when to use which, with web.config examples you can copy.