301 редирект через htaccess: рабочие правила без плагинов
Готовые примеры редиректов для смены адресов, склейки зеркал и переезда на https, порядок правил и как не поймать цикл.
Содержание
301 редирект в .htaccess пишется одной строкой: Redirect 301 со старого адреса на новый для точных совпадений, RewriteRule для шаблонов. Порядок правил важнее их содержания: срабатывает первое подходящее, и одно жадное правило вверху файла перекрывает всё, что стоит ниже.
Сайт переехал на новые адреса, старые ссылки отдают 404, позиции поплыли за две недели. Или обратная беда: кто-то нагородил правил, и браузер пишет «слишком много перенаправлений». 301 редирект через htaccess закрывает оба случая, если правила написаны в правильном порядке и без самодеятельности. Ниже готовые блоки, которые я копирую клиентам шестой год подряд, разбор очерёдности правил, диагностика цикла и проверка результата через curl. Плагины для этого не нужны ни на WordPress, ни на Битриксе.

Сразу оговорюсь: .htaccess работает только на Apache и совместимых с ним серверах. Если у вас чистый nginx, все примеры ниже придётся переписать в конфиг сервера. Про это отдельный раздел в конце, не пропустите его, если правила упорно не срабатывают.
Когда нужен 301, а когда лучше ничего не трогать
301 говорит поисковику: страница переехала навсегда, склей старый адрес с новым и передай туда накопленный вес. Яндекс и Google это делают, но не мгновенно: склейка занимает от двух недель до пары месяцев, в зависимости от того, как часто робот заходит на сайт. Браузер кэширует 301 жёстко и надолго, иногда до очистки профиля. Поэтому ошибочное правило живёт у посетителя дольше, чем на сервере, и это главный аргумент не экспериментировать на живом сайте.
302 и 307 означают «временно». Вес не передаётся, в индексе остаётся старый адрес. Это нормальный выбор для заглушки на время работ или для сезонной акции, которую через месяц уберут.
| Код | Что понимает робот | Кэш браузера | Типовой случай |
|---|---|---|---|
| 301 | переехало навсегда | долгий | смена URL, склейка зеркал, переход на https |
| 302 | временно, старый адрес живой | нет | заглушка на время работ, сезонная страница |
| 307 | временно, метод запроса сохраняется | нет | HSTS, редиректы для POST |
| 308 | навсегда, метод сохраняется | долгий | API, на сайте услуг почти не нужен |
| 410 | удалено окончательно, замены нет | нет | закрытая услуга, снятая с продажи позиция |
Есть третий вариант, о котором забывают: не делать редирект вообще. Если страница просто дублируется по двум адресам и обе версии нужны людям, правильнее поставить канонический адрес, а не резать один из URL насмерть. Разница и типовые ошибки разобраны в статье про канонические ссылки.
И ещё одна вещь, за которую я регулярно ругаюсь с подрядчиками. Не надо сваливать все удалённые страницы на главную. Робот видит массовый редирект нерелевантных URL на один адрес и обрабатывает их как обычные 404, только медленнее. Если замены странице нет, честнее отдать 404 или 410. Если замена есть, редирект должен вести на конкретный аналог, а не в корень сайта. Как искать такие хвосты, писал в разборе про битые ссылки и 404.
Redirect или RewriteRule: что выбрать
В Apache два разных модуля умеют перенаправлять, и путаница между ними стоит людям часов отладки.
Redirect и RedirectMatch живут в модуле mod_alias. Синтаксис короткий, работает по началу пути, регулярные выражения только у RedirectMatch. Этого хватает для точечных случаев.
# Одна страница на другую, точное совпадение по началу пути
Redirect 301 /uslugi/staraya-stranitsa /uslugi/novaya-stranitsa
# То же самое регуляркой, с якорем конца строки
RedirectMatch 301 ^/uslugi/staraya-stranitsa/?$ /uslugi/novaya-stranitsa/
Ловушка простого Redirect: он матчит префикс. Правило для /uslugi поймает и /uslugi-i-tseny, и /uslugi/podderzhka/, и хвост допишется к целевому адресу. Поэтому для чего-то сложнее одной статичной страницы я беру RedirectMatch с явным ^ и $ либо сразу mod_rewrite.
RewriteRule из mod_rewrite умеет условия: смотреть на домен, протокол, user-agent, наличие файла на диске. Всё, что зависит от контекста запроса, делается только им.
RewriteEngine On
# Условие + правило: редирект срабатывает, только если хост совпал
RewriteCond %{HTTP_HOST} ^old-domain.ru$ [NC]
RewriteRule ^(.*)$ https://new-domain.ru/$1 [R=301,L]
Практическое правило: если редирект зависит от чего-то кроме пути, это mod_rewrite. Если это одна фиксированная страница на другую, Redirect 301 короче и читается лучше. Смешивать оба модуля в одном файле можно, но помните, что mod_rewrite отрабатывает раньше mod_alias, и правило из RewriteRule перехватит запрос до того, как до него доберётся Redirect.
Флаги, которые нужны в 95 процентах случаев:
R=301отдать код 301 (без числа будет 302),Lпоследнее правило, дальше не обрабатывать,NCбез учёта регистра,QSAдописать исходную строку параметров к новому адресу,QSDнаоборот, выбросить параметры.
Готовые блоки правил
Дальше идут заготовки, которые я держу в сниппетах и правлю под клиента. Все они кладутся в .htaccess в корне сайта.
Страница на страницу
RewriteEngine On
RewriteRule ^kontakty-2$ /kontakty/ [R=301,L]
RewriteRule ^uslugi/podderzhka-saitov$ /uslugi/tehpodderzhka/ [R=301,L]
Обратите внимание: в файле .htaccess путь в шаблоне идёт без ведущего слеша, а в целевом адресе слеш нужен. Это первая причина, по которой скопированное из чужого конфига правило молчит.
Каталог целиком с сохранением структуры
# /catalog/uslugi/remont/ -> /uslugi/remont/
RewriteRule ^catalog/uslugi/(.*)$ /uslugi/$1 [R=301,L]
Скобки захватывают хвост, $1 подставляет его в новый адрес. Если хвост подставлять не надо и вся ветка схлопывается в одну страницу, скобки убираем, а в конец целевого адреса ставим ?, иначе Apache допишет параметры запроса:
RewriteRule ^arhiv/ /blog/? [R=301,L]
Смена домена
RewriteEngine On
RewriteCond %{HTTP_HOST} ^(www.)?old-domain.ru$ [NC]
RewriteRule ^(.*)$ https://new-domain.ru/$1 [R=301,L]
Такой блок переносит на новый домен всю карту адресов один в один. Если структура при переезде тоже менялась, сначала идут точечные правила на изменившиеся URL, и только потом общий блок. Полный порядок действий при смене домена, вместе с Вебмастером и sitemap, расписан в статье про переезд на новый домен.
С http на https
RewriteEngine On
RewriteCond %{HTTPS} !=on
RewriteRule ^(.*)$ https://%{HTTP_HOST}/$1 [R=301,L]
Если сайт стоит за прокси или балансировщиком (типичная история на shared-хостинге с LiteSpeed или на CDN), переменная %{HTTPS} может быть пустой даже при живом сертификате. Тогда сайт уйдёт в бесконечный цикл. Рабочая универсальная версия проверяет ещё и заголовок от прокси:
RewriteCond %{HTTPS} !=on
RewriteCond %{HTTP:X-Forwarded-Proto} !=https
RewriteRule ^(.*)$ https://%{HTTP_HOST}/$1 [R=301,L]
www и без www
Выберите одну версию как основную и склейте на неё вторую. Какую именно, значения не имеет, важно только не менять решение раз в полгода.
# Склейка на версию без www
RewriteCond %{HTTP_HOST} ^www.(.+)$ [NC]
RewriteRule ^(.*)$ https://%1/$1 [R=301,L]
%1 берётся из последнего RewriteCond, $1 из RewriteRule. Перепутать их легко, а результат будет выглядеть как редирект в никуда.
Хвосты, которые портят статистику
Четыре мелочи, которые дают дубли и размывают позиции. Каждая правится одной строкой.
# Всегда со слешем в конце, кроме реальных файлов
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_URI} !(/$|.)
RewriteRule ^(.*)$ /$1/ [R=301,L]
# Убрать index.php из адреса
RewriteCond %{THE_REQUEST} s/+index.php[s?] [NC]
RewriteRule ^ /? [R=301,L]
# Привести адрес к нижнему регистру (только для конкретной ветки)
RewriteMap lc int:tolower
Последний пример работает не везде: RewriteMap объявляется в конфиге сервера, а в .htaccess его использовать нельзя. На shared-хостинге проблему с регистром проще решить точечными правилами на конкретные адреса.
Порядок правил решает всё
Apache читает .htaccess сверху вниз и на первом совпавшем правиле с флагом L останавливается. Отсюда два следствия, из-за которых обычно всё и ломается.
Первое: общие правила ставим ниже частных. Если блок «весь старый домен на новый» стоит первым, точечные правила под ним никогда не сработают. Робот получит редирект на новый домен по старому пути и там 404.
Второе: правила самой CMS должны идти последними. У WordPress это блок между комментариями # BEGIN WordPress и # END WordPress, у Битрикса свой набор в корневом .htaccess. Свои редиректы кладём выше этого блока, иначе фронт-контроллер CMS перехватит запрос раньше и отдаст 404 своими силами.
Рабочая структура файла, которую я держу у всех клиентов:
# 1. Протокол и зеркала
RewriteEngine On
RewriteCond %{HTTPS} !=on
RewriteCond %{HTTP:X-Forwarded-Proto} !=https
RewriteRule ^(.*)$ https://%{HTTP_HOST}/$1 [R=301,L]
RewriteCond %{HTTP_HOST} ^www.(.+)$ [NC]
RewriteRule ^(.*)$ https://%1/$1 [R=301,L]
# 2. Точечные редиректы страниц
RewriteRule ^kontakty-2$ /kontakty/ [R=301,L]
# 3. Редиректы разделов
RewriteRule ^catalog/uslugi/(.*)$ /uslugi/$1 [R=301,L]
# BEGIN WordPress
# ... штатный блок CMS, не трогаем ...
# END WordPress
Ещё одна деталь про порядок: кэширующие директивы и правила блокировки ботов живут в этом же файле и тоже конфликтуют по очерёдности. Если у вас там уже настроены заголовки кэша или запреты по user-agent, добавляйте редиректы над ними и проверяйте, что ничего не поехало.
Цикл редиректов: как поймать и разорвать
Симптом узнаваемый: браузер пишет «ERR_TOO_MANY_REDIRECTS» или «слишком много перенаправлений», сайт не открывается вообще. Причин у цикла всего несколько, и все они лечатся за десять минут, если знать, где смотреть.
Правило редиректит само на себя. Классика с трейлинг-слешем: правило добавляет слеш, а CMS его снимает. Или наоборот. Лечится добавлением условия, которое исключает уже правильные адреса.
Сервер не видит https. Разобрано выше: %{HTTPS} пустая за прокси, правило гонит на https бесконечно. Проверяется добавлением условия на X-Forwarded-Proto.
Редирект настроен в двух местах сразу. У WordPress это любимая история: правило есть в .htaccess, плагин редиректов делает то же самое, а поверх ещё стоит канонический редирект ядра. Правильно оставить один источник правды. Мой выбор всегда .htaccess, потому что он отрабатывает до загрузки PHP и не зависит от того, жив ли сайт.
Разъехались настройки CMS и сервера. В WordPress адреса siteurl и home записаны в базе. Если там http://www.site.ru, а .htaccess гонит на https://site.ru, получаем качели. Проверить и поправить:
wp option get siteurl
wp option get home
wp option update siteurl 'https://site.ru'
wp option update home 'https://site.ru'
Быстрая диагностика без гадания: смотрим всю цепочку целиком, а не конечный результат.
curl -sIL -A "Mozilla/5.0" http://www.site.ru/staraya-stranitsa
| grep -Ei "^(HTTP/|location:)"
Вывод покажет каждый шаг: сколько было переходов, какие коды, куда вёл каждый. Если в ответе повторяются два адреса по кругу, вы нашли пару правил, которые дерутся. Здоровая цепочка после переезда это один или два перехода, не больше. Три и выше уже стоит распутывать: каждый лишний хоп это потерянное время загрузки и риск, что робот не дойдёт до конца.
Если сайт лёг совсем и админка недоступна, аварийный ход простой: переименовать .htaccess в .htaccess.bak через файловый менеджер хостинга, убедиться, что сайт поднялся, и разбирать правила уже на живом сайте. У WordPress штатный блок восстанавливается сам при сохранении постоянных ссылок в админке.
Проверка после настройки
Онлайн-чекеры удобны для одиночной ссылки, но для переезда на сотню адресов нужен список. Я делаю так: выгружаю все старые URL (из sitemap, из отчёта Вебмастера, из выгрузки старой CMS), кладу в файл и прогоняю пачкой.
while read url; do
code=$(curl -s -o /dev/null -w "%{http_code}" -I "$url")
target=$(curl -s -o /dev/null -w "%{redirect_url}" -I "$url")
echo "$code $url -> $target"
done < old-urls.txt
На выходе таблица, в которой глазами видно все 404 и все редиректы в неожиданные места. Пять минут работы вместо часа кликов по чекерам.
Чек-лист проверки после включения правил:
- главная открывается по всем четырём вариантам адреса (http, https, с www, без www) и все три лишних варианта дают ровно один 301 на основной;
- десяток случайных внутренних страниц отдают 200, а не цепочку из трёх переходов;
- картинки, CSS и JS грузятся по https без смешанного контента;
- формы отправляются и заявка доходит (редирект с POST на другой домен теряет данные, если стоит 301 вместо 307);
- админка открывается и сохраняет изменения;
- в Яндекс Вебмастере через несколько дней в разделе с ошибками не растёт число недоступных страниц;
- sitemap.xml содержит только конечные адреса, без старых URL, которые сами редиректят;
- внутренние ссылки в контенте ведут на новые адреса напрямую, а не через редирект.
Последний пункт пропускают почти все. Редирект нужен для внешних ссылок и для роботов, а внутри сайта ссылки надо переписать в базе. Иначе каждый клик посетителя делает лишний переход, а вы годами таскаете технический долг.
Когда htaccess просто не работает
Бывает, что правило написано верно, а эффекта ноль. Проверьте по порядку.
Файл не читается сервером. На nginx без Apache .htaccess игнорируется полностью, никаких ошибок при этом не будет. Проверить, что за сервер у вас, можно одной командой:
curl -sI https://site.ru | grep -i "^server:"
Если в ответе nginx без упоминания Apache, правила надо переносить в конфиг сервера, а на shared-хостинге это делается только через поддержку. Чем отличаются связки Apache, nginx и LiteSpeed и что из этого стоит просить у хостера, разбирал в статье про выбор веб-сервера.
Запрещено переопределение. Директива AllowOverride None в конфиге сервера отключает .htaccess для директории. На нормальном shared-хостинге такого не встречал, на VPS после ручной настройки Apache встречал регулярно.
Модуль не подключен. Без mod_rewrite все блоки с RewriteRule либо молчат, либо роняют сайт в 500. Оберните правила в проверку, чтобы не положить сайт целиком:
<IfModule mod_rewrite.c>
RewriteEngine On
# правила
</IfModule>
Кэш браузера. Вы уже поймали старый 301, и браузер честно ведёт вас по нему, не спрашивая сервер. Проверять правила надо в приватном окне или через curl, а не в основном профиле. Я на этом однажды потерял сорок минут, доказывая клиенту, что редирект работает.
Права на файл. Если .htaccess лежит с правами 600 и владельцем root, веб-сервер его не прочитает. Нормально это 644 для файла и 755 для директории.
Что делать дальше
Порядок работ при переезде адресов у меня выглядит так. Сначала собираю полный список старых URL и составляю карту соответствия: каждому старому адресу конкретный новый, а не главная. Потом пишу правила локально или на тестовой копии и прогоняю curl по всему списку. Только после этого правила уезжают на боевой сайт, и там же сразу проверяются заново. В конце переписываю внутренние ссылки в базе и обновляю sitemap.
Разовая настройка редиректов для сайта на 30-50 страниц занимает у меня 2-4 часа вместе с картой адресов и проверкой. Если переезд большой и адресов несколько сотен, время уходит в основном на карту соответствия, а не на сам .htaccess. У клиентов на абонентском обслуживании такие работы обычно попадают в месячный лимит часов, отдельной сметы не требуют. Условия и что входит в тарифы, смотрите в разделе с ценами: годовой пакет 35 000 рублей, помесячный от 15 000.
Если сайт уже лежит с циклом редиректов или после переезда посыпались 404 и позиции, это тот случай, когда каждый день ожидания стоит трафика. Напишите через форму на главной, опишите, что менялось и когда, и приложите вывод curl -sIL по проблемному адресу. С этими данными диагностика занимает минуты, а не переписку на три дня.
Частые вопросы
Как сделать 301 редирект через htaccess
Для одиночного адреса хватает директивы Redirect 301 со старым и новым путём. Для шаблона нужен RewriteRule с флагами R=301 и L. Файл кладут в корень сайта, рядом с index.php. Проверять надо ответ сервера, а не глазами в браузере: браузер кэширует редиректы надолго.
Чем Redirect отличается от RewriteRule
Redirect проще и подходит, когда старый адрес переезжает на новый один в один. RewriteRule нужен там, где есть шаблон: перенос раздела целиком, склейка параметров, условия по домену или протоколу.
Почему браузер пишет про слишком много перенаправлений
Правила зациклились: одно ведёт на второе, второе обратно на первое. Чаще всего виноват редирект на самого себя, который срабатывает уже после переписывания адреса. Ловится по цепочке ответов сервера, а не по коду.
Почему htaccess не работает
Либо сервер не Apache: на чистом nginx файл просто игнорируется, правила переносят в конфиг сервера. Либо для директории запрещено переопределение настроек, и тогда хостинг должен разрешить его в конфигурации.