Redirect 301 or the RewriteCond and RewriteRule pair

The one-line Redirect

Redirect 301 /old /new is mod_alias, and it is the shortest correct answer for one move on a server where Apache itself ends TLS. The target is a bare path, so Apache builds the Location from the scheme it sees.

On a host behind a TLS proxy Apache sees plain HTTP, so the visitor gets an extra http hop:

curl -s -o /dev/null -w "%{http_code} %{redirect_url}\n" https://example.com/user_profile/

301 http://example.com/services/user_profile/

The browser follows that to http://, and the HTTPS rule sends it back to https://: two hops, and the middle request is unencrypted. Where TLS is in Apache (DreamHost, or a VPS with a certificate from certbot) the same bare target is fine, and the first hop is already https.

An absolute target

Write the full URL and mod_alias has nothing to guess:

Redirect 301 /old/page.php https://example.com/new/page.php

This is right everywhere, proxy or no proxy. The cost is that the file now names one host: the same line sends a visitor on a test domain, a staging copy or localhost to production. With many moves that is a lot of host names to keep in sync.

The RewriteCond and RewriteRule pair

The pair tests the path in a condition and builds the target from variables:

RewriteCond %{REQUEST_URI} ^/old/page\.php$ RewriteRule ^ %{ENV:SCHEME}://%{HTTP_HOST}/new/page.php [R=301,L]

The variable is set once, near the top of the file. On Apache 2.4.68 with mod_php this block answered over https:

RewriteEngine On RewriteRule ^ - [E=SCHEME:http] RewriteCond %{HTTPS} =on [OR] RewriteCond %{HTTP:X-Forwarded-Proto} =https RewriteRule ^ - [E=SCHEME:https] RewriteCond %{REQUEST_URI} ^/test-htaccess/old$ RewriteRule ^ %{ENV:SCHEME}://%{HTTP_HOST}/test-htaccess/new [R=301,L]

curl -s -o /dev/null -w "%{http_code} %{redirect_url}\n" http://localhost:8080/test-htaccess/old

301 https://localhost:8080/test-htaccess/new

The scheme comes from an environment variable you set once at the top of the file, so the same rule keeps https behind a proxy and stays on http on localhost. The host comes from the request, so the file works on localhost, dev and production without edits. A condition can also test the query string, a header, a file or a folder, and a group captured in the condition comes back as %1:

RewriteCond %{REQUEST_URI} ^/old/(.*)$ RewriteRule ^ %{ENV:SCHEME}://%{HTTP_HOST}/new/%1 [R=301,L]

%1, not $1: the capture is in the condition, not in the rule pattern. The rewritten rules on this site use exactly this shape, with %{ENV:AREDEL_SCHEME} instead of %{ENV:SCHEME}.

Mixing mod_alias and mod_rewrite

mod_rewrite runs before mod_alias, whatever the line order. Two rules that look sequential can therefore fire in the opposite order:

Redirect 301 /test-htaccess/old /test-htaccess/alias-target RewriteEngine On RewriteCond %{REQUEST_URI} ^/test-htaccess/old$ RewriteRule ^ http://%{HTTP_HOST}/test-htaccess/rewrite-target [R=301,L]

curl -sS -o /dev/null -w "%{http_code} %{redirect_url}\n" http://localhost:8080/test-htaccess/old

301 http://localhost:8080/test-htaccess/rewrite-target

On Apache 2.4.68 with mod_php the rewrite wins even though its lines are lower: the answer is /test-htaccess/rewrite-target, not the mod_alias target. The fix is not to reorder them; it is to keep one system per file.

Redirect inside If

Since Apache 2.4.19 a Redirect may sit inside <If>, but only the one-argument form evaluates the expression. Put the source in the condition:

<If "%{REQUEST_URI} == '/test-htaccess/old'"> Redirect 301 "https://%{HTTP_HOST}/test-htaccess/new" </If>

On Apache 2.4.68 with mod_php that answers 301 with https://localhost:8080/test-htaccess/new; another path answers 404. The two-argument form keeps the text literal:

<If "%{HTTP_HOST} =~ /localhost/"> Redirect 301 "/test-htaccess/old" "https://%{HTTP_HOST}/test-htaccess/new" </If>

That form answers 301 https://%{HTTP_HOST}/test-htaccess/new: a broken Location, so use the one-argument form.

Which one to use

The proxy case, with the full history of one site, is in Redirects behind an HTTPS proxy. The two shared hosts that need opposite conditions are described in Beget and DreamHost; the move itself is a chapter of the moving guide.

Article author: Arthur Isaev