Apache redirects: Redirect, RedirectMatch, HTTPS and www rules

Updated November 2026

A redirect tells the browser "this page is now over there" with a 3xx status and a Location header. In Apache httpd 2.4 most redirects need one Redirect line from mod_alias; mod_rewrite is only needed when the decision depends on the host name, the protocol or other conditions. This guide covers both, which status code to choose, and how to move a whole site without losing visitors or search rankings.

Redirect and RedirectMatch

mod_alias is enabled by default on Debian/Ubuntu and RHEL-family systems. Its directives work in the server config, in a virtual host and in .htaccess (with AllowOverride FileInfo).

# One page
Redirect permanent "/old-page.html" "https://www.example.com/new-page.html"

# A whole folder, to another host
Redirect permanent "/blog" "https://blog.example.com"

# A pattern: /news/2001/story.php becomes /archive/2001/story.html
RedirectMatch permanent "^/news/(\d{4})/(.*)\.php$" "https://www.example.com/archive/$1/$2.html"

# Removed for good
Redirect gone "/old-offer.html"

How Redirect matches:

RedirectMatch uses a regular expression on the path, with $1, $2 for groups. Anchor it with ^ and $, or it matches anywhere in the path. RedirectPermanent and RedirectTemp are short forms of Redirect permanent and Redirect temp.

Which status code

CodeMeaningMethod on the next requestUse it for
301Moved PermanentlyGET stays GET; browsers usually turn POST into GETMoved pages, HTTPS, www, domain changes
308Permanent RedirectMethod and body keptPermanent moves of APIs and forms that receive POST
302Found (temporary)As 301Short-term moves, testing a new redirect
307Temporary RedirectMethod and body keptTemporary moves where POST must survive
303See OtherAlways GETAfter a form POST, to a result page
410Gone (not a redirect)–Pages removed for good with no replacement

301 and 308 are cacheable by default, so browsers may remember them, and Google treats both as permanent: it shows the new URL in search results. For 302, 303 and 307 it usually keeps showing the old URL. So use permanent codes only when you are sure, and test new redirects as 302 first: a mistaken 301 can stay in visitors' browser caches long after you fix the config.

HTTP to HTTPS

The cleanest way is a small port-80 virtual host that only redirects, next to the real HTTPS one (set up TLS first: HTTPS and TLS):

<VirtualHost *:80>
    ServerName www.example.com
    ServerAlias example.com
    Redirect permanent "/" "https://www.example.com/"
</VirtualHost>

<VirtualHost *:443>
    ServerName www.example.com
    DocumentRoot /var/www/example.com/public
    # SSLEngine on, certificate lines, logs ...
</VirtualHost>

Because Redirect keeps the rest of the path, http://example.com/a/b.html?x=1 lands on https://www.example.com/a/b.html?x=1 in one step. On Debian/Ubuntu the files live in /etc/apache2/sites-available/ (enable with sudo a2ensite); on RHEL-family systems in /etc/httpd/conf.d/. See Virtual hosts.

If you can only use .htaccess, mod_rewrite does it:

RewriteEngine On
RewriteCond %{HTTPS} !=on
RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]

Do not add [NE] here: %{REQUEST_URI} is already decoded, and without escaping a path such as /a%20b.html would be sent back with a raw space. Behind a load balancer or CDN that terminates TLS, Apache sees every request as plain HTTP and this rule loops forever. Then test the header the proxy sets instead, for example RewriteCond %{HTTP:X-Forwarded-Proto} !=https, and only if requests can reach Apache through that proxy alone.

Once HTTPS works everywhere, add an HSTS header so browsers skip the HTTP step next time.

www or non-www

Pick one host name and redirect the other, so that search engines and cookies see a single site. With access to the server config, give the unwanted name its own virtual host:

<VirtualHost *:443>
    ServerName example.com
    # same certificate lines as the main site; the certificate must cover example.com
    Redirect permanent "/" "https://www.example.com/"
</VirtualHost>

Or, inside the one virtual host that answers both names:

<If "%{HTTP_HOST} == 'example.com'">
    Redirect permanent "/" "https://www.example.com/"
</If>

For .htaccess, this rule does HTTPS and www in a single redirect, so visitors never go through two hops:

RewriteEngine On
RewriteCond %{HTTPS} !=on [OR]
RewriteCond %{HTTP_HOST} !^www\. [NC]
RewriteCond %{HTTP_HOST} ^(?:www\.)?(.+)$
RewriteRule ^ https://www.%1%{REQUEST_URI} [R=301,L]

The last condition captures the host without www. into %1. To go the other way (drop the www), use RewriteCond %{HTTP_HOST} ^www\.(.+)$ [NC] and the target https://%1%{REQUEST_URI}.

Keeping old URLs alive after a move

Links, bookmarks and search results pointing at your old addresses keep arriving for years. A move without redirects turns all of them into 404 errors. A good plan:

  1. List the old URLs from your sitemap, CMS export and access logs (requests that returned 200 in the last year are a good start).
  2. Map each one to its new address. Send it to the closest matching page, not to the home page. If nothing replaces it, return 410.
  3. Redirect straight to the final URL. Avoid chains such as old → http new → https new. Google advises going directly to the final destination and keeping chains short.
  4. Keep the redirects as long as possible. Google's site-move guidance says generally at least a year; for visitors, keeping them indefinitely costs almost nothing.
  5. Update your own links and sitemaps to the new URLs so your pages do not depend on the redirects.

Hundreds of redirects: RewriteMap

For a long list, a lookup file is easier to maintain than hundreds of Redirect lines. RewriteMap only works in the server or virtual host config:

# /etc/apache2/redirects.map (or /etc/httpd/conf/redirects.map)
# old path              new path
/old/about.php          /about/
/old/contact.php        /contact/

# in the <VirtualHost>
RewriteEngine On
RewriteMap moved "txt:/etc/apache2/redirects.map"
RewriteCond ${moved:%{REQUEST_URI}} !=""
RewriteRule ^ ${moved:%{REQUEST_URI}} [R=301,L]

Paths not in the file are not affected; query strings are passed on. Apache re-reads a changed txt: map automatically.

Test and avoid loops

curl -sI http://example.com/a/b.html | grep -iE '^(HTTP|location)'
HTTP/1.1 301 Moved Permanently
Location: https://www.example.com/a/b.html

curl -sIL -o /dev/null -w '%{num_redirects} redirects, final %{url_effective}\n' http://example.com/a/b.html
1 redirects, final https://www.example.com/a/b.html

Do not mix Redirect and RewriteRule for the same URLs. In a virtual host mod_rewrite runs first; in .htaccess mod_alias runs first, so the same lines can behave differently after you move them. More on rules and flags: mod_rewrite.

Sources