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, поэтому используйте форму с одним аргументом.
Что выбрать
- Один простой перенос, TLS в Apache, одно имя хоста: используйте Redirect 301.
- Много переносов, несколько хостов, прокси спереди или любое условие по строке запроса, заголовку или файлу: используйте пару.
- Не смешивайте обе системы в одном файле.
- Проверяйте через curl, не следуя редиректам: -o /dev/null -w "%{http_code} %{redirect_url}\n". Цель - один шаг до конечного https-адреса.
Случай с прокси и полная история одного сайта: Редиректы за HTTPS-прокси. Два общих хостинга, которым нужны противоположные условия, описаны в статьях Beget и DreamHost; сам переезд — глава руководства по переносу.
Автор статьи: Arthur Isaev