.htaccess redirect: how to write a 301 or 302 redirect in Apache

How to write a 301 or 302 redirect in the .htaccess file, which directive to choose (Redirect, RedirectMatch or RewriteRule), and the common cases: domain change, HTTPS, home page, WordPress.

.htaccess redirect: how to write a 301 or 302 redirect in Apache

The .htaccess file lets you redirect a URL directly on an Apache server, without touching the main configuration. This guide shows how to write a 301 or 302 in it, which directive to choose (Redirect, RedirectMatch or RewriteRule), and the most common cases: moving a whole domain, forcing HTTPS, changing the home page and WordPress.

ℹ️

Before you start

It is not the only option: the vhost is faster, and a CMS plugin is easier for marketing teams to maintain. To choose, read our article .htaccess, vhost or plugin: where to place your redirect plan?

Prerequisites: the .htaccess file and the Apache modules

It sits at the root of the site (the folder that holds the home page), or in a subfolder to act on that directory only. It is hidden: show hidden files in your FTP client or your hosting file manager. If it does not exist, create a plain text file named .htaccess.

Two modules do this work:

  • mod_alias, which provides the Redirect, RedirectPermanent and RedirectMatch directives;
  • mod_rewrite, which provides RewriteEngine, RewriteCond and RewriteRule, for conditional cases.

For it to be taken into account, the main setup must allow these directives (AllowOverride All, or at least FileInfo). Otherwise it is simply ignored. On shared hosting, access is almost always already granted. Nginx has no equivalent: everything goes in its server block.

301 or 302 redirect: permanent or temporary?

A 301 means the page has moved for good: search engines understand the address has changed, and Google passes its SEO value and link equity to the destination, without duplicate content. Use it for a migration, a domain change or a renamed page.

A 302 means the move is temporary: the original address stays indexed, because it is meant to come back. Use it for maintenance, a time-limited promotion or a test. A 302 left in place for months ends up treated like a 301, but it is better to be explicit from the start.

Redirect and RedirectPermanent: the simple redirect

To send one page to another, Redirect is enough. It takes the code, the old path (without the domain) and the full destination address:

Redirect 301 /old-page.html https://www.mysite.com/new-page/
RedirectPermanent /old-folder/ https://www.mysite.com/new-folder/
Redirect 302 /promo https://www.mysite.com/sale/

RedirectPermanent is a shortcut for Redirect 301, and RedirectTemp for Redirect 302. Beware: Redirect matches a prefix. The second line also sends /old-folder/page.html to /new-folder/page.html, which is handy for a whole directory but can surprise users.

RedirectMatch: redirect with a regular expression

RedirectMatch works like Redirect, but with a regular expression. It handles a whole family of addresses in one line, reusing the captured parts with $1, $2, etc.:

# Old .php pages to addresses without an extension
RedirectMatch 301 ^/blog/(.*)\.php$ https://www.mysite.com/blog/$1

# One exact URL (^ and $ prevent the prefix effect)
RedirectMatch 301 ^/contact\.html$ https://www.mysite.com/contact/

RewriteRule and RewriteCond: advanced redirects

This module is the most flexible: each line can depend on a condition on the host name, the protocol, the parameters or any variable. The engine is turned on once, at the top, with RewriteEngine On.

RewriteEngine On

# One specific page, dropping the query string
RewriteRule ^old-page/?$ https://www.mysite.com/new-page/ [R=301,L,NC,QSD]

# A URL with a parameter: /product.php?id=12
RewriteCond %{QUERY_STRING} ^id=12$
RewriteRule ^product\.php$ https://www.mysite.com/products/chair/ [R=301,L,QSD]

The flags in square brackets change its behaviour:

  • R=301 or R=302: the HTTP code sent back (without R, it is an internal rewrite the visitor never sees);
  • L: last one, processing stops there;
  • NC: ignores upper and lower case;
  • QSD: drops the original query string (Apache 2.4); QSA keeps it and appends it.

Here, the tested pattern does not start with a slash: write ^old-page, not ^/old-page. It is the most common mistake when copying an example written for a vhost.

Which directive should you use?

NeedDirective
One page to anotherRedirect 301
A whole directory, with its subpagesRedirect 301 (prefix effect)
A family of addresses matching a patternRedirectMatch 301
Based on host name, protocol or a parameterRewriteCond + RewriteRule
A CMS that already rewrites its URLs (WordPress, PrestaShop)RewriteRule

Avoid mixing both modules for the same addresses: mod_rewrite runs before mod_alias, whatever the order of the lines, which gives results that are extremely hard to predict. On a site that already uses it, stick to one module.

Redirect a whole domain with .htaccess

When you change domain, put this on the old site, under the RewriteEngine On line. Each address goes to the same page on the new one, path included:

RewriteCond %{HTTP_HOST} ^(www\.)?old-domain\.com$ [NC]
RewriteRule ^(.*)$ https://www.new-domain.com/$1 [R=301,L]

This catch-all only works if the paths have stayed the same. If the addresses have changed, you need a page-by-page redirect plan, placed before this catch-all. Also remember to renew the old domain name: without it, none of these redirects work any more.

Force HTTPS (and www) in .htaccess

To send all HTTP traffic to HTTPS, and the non-www version to www, in a single hop:

RewriteCond %{HTTPS} off [OR]
RewriteCond %{HTTP_HOST} !^www\. [NC]
RewriteRule ^(.*)$ https://www.mysite.com/$1 [R=301,L]

Behind a proxy or a CDN (Cloudflare, load balancer), %{HTTPS} is always off and it loops: test the %{HTTP:X-Forwarded-Proto} header instead.

Change the home page with .htaccess

To make another file serve as the home page, you do not need a 301 but DirectoryIndex. The first file found in the list is served, without changing the address shown:

DirectoryIndex home.html index.php index.html

If the home page really must move (for example / to /en/), limit it to the root, with RedirectMatch 301 ^/$ https://www.mysite.com/en/. Without ^/$, every page would go to that destination.

301 redirect in the WordPress .htaccess

WordPress writes its own rules between the # BEGIN WordPress and # END WordPress lines, and rewrites them every time the permalinks change. Always place yours above this block: below it, WordPress catches the request first, and inside it, they would be erased. A plugin in the admin menu is also possible, but it loads PHP on every visit.

# SEO redirects
RewriteEngine On
RewriteRule ^old-post/?$ https://www.mysite.com/new-post/ [R=301,L]

# BEGIN WordPress
...
# END WordPress

Cancel a redirect without removing the other rules

  1. Make a backup copy of the file before any change.
  2. Open it in a text editor and find the line of the redirect (Redirect, RedirectMatch or RewriteRule, with any RewriteCond lines just above it).
  3. Delete these lines, or comment them out with a # at the start of the line to keep a record.
  4. Save and upload it again.
  5. Test in a private window: browsers cache a 301 and may keep applying it after it has been removed.

Test an .htaccess redirect

The most reliable check is to read the HTTP headers, without the browser cache. On the command line, curl -I https://www.mysite.com/old-page/ must show the 301 status and a Location header with the new URL. Also make sure there is only one hop: a chain (HTTP to HTTPS, then to www, then to the final page) slows down crawlers and dilutes the signal. A 500 error right after saving means a syntax error: restore the backup.

For businesses migrating several hundred pages, Swan generates the lines for this file or for the vhost, and automatically tests every old address.

<p><code>Redirect</code> simply sends a path to an address. <code>RewriteRule</code> accepts regular expressions and conditions on the domain, the protocol or the parameters. For a single page, <code>Redirect 301</code> is enough.</p>
<p>The usual causes: a host that ignores the file, an extra slash at the start of a pattern, a line placed below the WordPress block, or an old 301 cached by the browser.</p>
<p>There is no technical limit, but it is read in full on every request. Beyond a few thousand lines, put the plan in the vhost instead.</p>

Swan generates your redirect plan for .htaccess or the vhost, and checks every URL.

The SEO migration checklist, as a PDF. 20 pages of checks to tick off, from the decision to migrate to monitoring after go-live.

Read next

All posts