Redirect 301 или пара RewriteCond и RewriteRule

Redirect в одну строку

Redirect 301 /old /new - это mod_alias, и это самый короткий правильный ответ для одного переноса на сервере, где TLS заканчивает сам Apache. Цель - обычный путь, поэтому Apache строит Location из схемы, которую видит сам.

На хостинге за TLS-прокси Apache видит обычный HTTP, и посетитель получает лишний шаг на http:

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

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

Браузер идёт по этому адресу на http://, а правило HTTPS отправляет его обратно на https://: два шага, и средний запрос не шифруется. Там, где TLS в Apache (DreamHost или VPS с сертификатом certbot), та же цель из одного пути безопасна, и первый шаг уже https.

Абсолютный адрес

Напишите полный URL, и mod_alias нечего угадывать:

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

Это правильно везде, с прокси и без. Цена в том, что файл теперь называет один хост: та же строка отправит посетителя тестового домена, копии для разработки или localhost на продакшен. При многих переносах это много имён хостов, которые надо синхронизировать.

Пара RewriteCond и RewriteRule

Пара проверяет путь в условии и собирает цель из переменных:

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

Переменная задаётся один раз, в начале файла. На Apache 2.4.68 с mod_php этот блок ответил по 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

Схема берётся из переменной окружения, которую вы задаёте один раз в начале файла, поэтому одно правило сохраняет https за прокси и остаётся на http на localhost. Хост берётся из запроса, поэтому файл работает на localhost, dev и продакшене без правок. Условие может также проверять строку запроса, заголовок, файл или папку, а группа, захваченная в условии, возвращается как %1:

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

%1, а не $1: захват находится в условии, а не в шаблоне правила. Переписанные правила этого сайта устроены именно так, только с %{ENV:AREDEL_SCHEME} вместо %{ENV:SCHEME}.

Смешивание mod_alias и mod_rewrite

mod_rewrite выполняется раньше mod_alias независимо от порядка строк. Два правила, которые выглядят последовательными, могут сработать в обратном порядке:

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

На Apache 2.4.68 с mod_php rewrite побеждает, хотя его строки ниже: ответ - /test-htaccess/rewrite-target, а не цель mod_alias. Исправление не в перестановке строк, а в том, чтобы держать в одном файле одну систему.

Redirect внутри If

Начиная с Apache 2.4.19 Redirect может стоять внутри <If>, но выражение вычисляет только форма с одним аргументом. Источник ставьте в условие:

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

На Apache 2.4.68 с mod_php это отвечает 301 с https://localhost:8080/test-htaccess/new; другой путь отвечает 404. Форма с двумя аргументами оставляет текст буквальным:

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

Она отвечает 301 https://%{HTTP_HOST}/test-htaccess/new: сломанный Location, поэтому используйте форму с одним аргументом.

Что выбрать

Случай с прокси и полная история одного сайта: Редиректы за HTTPS-прокси. Два общих хостинга, которым нужны противоположные условия, описаны в статьях Beget и DreamHost; сам переезд — глава руководства по переносу.

Автор статьи: Arthur Isaev