Редиректы в .htaccess для WordPress: как правильно делать 301
Переносим URL без потери позиций: какую директиву редиректа .htaccess выбрать в WordPress, где именно должны стоять правила и как всё проверить, не заблокировав себе доступ.
Опубликовано
Переносите URL и хотите, чтобы старый адрес сохранил позиции? Нужен 301 в .htaccess: поставленный там, где WordPress его не сотрёт, написанный правильной директивой и указывающий на тот самый URL, который WordPress реально отдаёт. Ошибитесь в любом из трёх пунктов — получите цепочку редиректов, петлю или 500, который закроет вам доступ в wp-admin.
Сначала выбор директивы, потом правило размещения, потом проверка — именно в этом порядке.
Какая директива: Redirect, RedirectMatch или RewriteRule
В игре два модуля Apache, и они не взаимозаменяемы.
Redirect (mod_alias) — один известный путь на один адрес. Самое простое, что работает:
Redirect 301 /old-page/ https://example.com/new-page/
Две особенности, о которых надо знать. Директива сопоставляет префикс пути, а не точную строку, поэтому /old-page/ ловит и /old-page/anything/, дописывая /anything/ к адресу назначения. И она автоматически переносит строку запроса.
RedirectMatch (mod_alias) — одно регулярное выражение на множество URL. Так переносят целый раздел:
RedirectMatch 301 ^/blog/(.*)$ https://example.com/articles/$1
Захватывающая группа (.*) становится $1 в адресе назначения, поэтому /blog/hello-world/ уезжает на /articles/hello-world/. Обратите внимание на ведущий слеш: шаблоны mod_alias сопоставляются с полным путём URL.
RewriteRule (mod_rewrite) — обязателен, когда редирект зависит от условия: хоста, протокола, строки запроса, user agent. Ничто в mod_alias проверить это не может.
RewriteEngine On
RewriteCond %{HTTP_HOST} ^example\.com$ [NC]
RewriteRule ^(.*)$ https://www.example.com/$1 [R=301,L]
У шаблона в поуровневом .htaccess ведущий слеш срезается — поэтому здесь ^(.*)$, а в примере с mod_alias было ^/blog/. Путаница в этом месте — самая частая причина того, почему скопированное откуда-то правило молча ничего не делает.
По умолчанию берите mod_alias. К RewriteRule тянитесь только тогда, когда нужно условие. Меньше регулярных выражений — меньше способов ошибиться.
Где должны стоять правила
Свои редиректы ставятся выше строки # BEGIN WordPress. Не внутри неё.
# --- custom redirects ---
Redirect 301 /old-page/ https://example.com/new-page/
RedirectMatch 301 ^/blog/(.*)$ https://example.com/articles/$1
# BEGIN WordPress
# The directives (lines) between "BEGIN WordPress" and "END WordPress" are
# dynamically generated, and should only be modified via WordPress filters.
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
RewriteBase /
RewriteRule ^index\.php$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php [L]
</IfModule>
# END WordPress
Две независимые причины:
- WordPress переписывает свой блок сам. Сохранение «Настройки → Постоянные ссылки» вызывает
flush_rewrite_rules(), которая выбрасывает всё между маркерами и генерирует заново. Правило, положенное внутрь, исчезнет, как только кто-нибудь откроет этот экран, — возможно, спустя месяцы, и никто не свяжет два события. - Порядок решает, кто победит. Блок WordPress заканчивается общим правилом, отправляющим каждый запрос, который не файл и не каталог, в
index.php.RewriteRule, стоящее после него, не выполнится никогда.
Одно честное усложнение: mod_alias и mod_rewrite на самом деле выполняются не в порядке строк файла. Apache обрабатывает mod_alias на этапе трансляции URL, а поуровневый mod_rewrite позже, на этапе fixup, — поэтому строка Redirect может победить RewriteRule, стоящее в файле выше. Если вам понадобились оба на пересекающихся путях, выберите для этого пути один модуль и оставайтесь внутри него. Разбираться в схватке mod_alias против mod_rewrite не стоит потраченного часа.
Слеш в конце адреса
Собственный канонический редирект WordPress добавляет слеш в конец большинства постоянных ссылок. Поэтому вот это:
Redirect 301 /old-page/ https://example.com/new-page
даёт два перехода — ваш 301 на адрес без слеша, а затем собственный 301 WordPress, который слеш добавляет. Цепочки по-прежнему передают сигналы ранжирования, но тратят краулинговый бюджет и добавляют лишний круг для каждого посетителя.
Сначала откройте адрес назначения в браузере, скопируйте URL ровно в том виде, в каком он устоялся в адресной строке, и используйте его. Если ваша структура постоянных ссылок заканчивается на .html или вообще без слеша — подстраивайтесь под это. Универсально правильного ответа тут нет, есть только «повторяйте то, что отдаёт WordPress».
Как проверять, не заблокировав себе доступ
.htaccess читается на каждом запросе. Синтаксическая ошибка возвращает 500 для всего сайта, включая wp-admin, поэтому путь к отступлению должен существовать до того, как он понадобится.
Сначала сделайте резервную копию файла. По SSH:
cp /path/to/wordpress/.htaccess /path/to/wordpress/.htaccess.bak
Если сайт отдаёт 500, просто верните копию поверх сломанного файла — сайт поднимется сразу. Держите SFTP-сессию уже открытой в соседнем окне: логиниться с нуля, когда сайт лежит, — вот с этого и начинается паника. Учтите, что apachectl configtest не разбирает .htaccess, поэтому он отрапортует о здоровой конфигурации, пока ваш сайт мёртв.
Проверяйте через curl, а не в браузере. Браузеры жёстко кэшируют ответы 301 и с удовольствием покажут вам вчерашний результат:
curl -sI https://example.com/old-page/ | head -n 5
Смотрите на две вещи: в строке статуса должно быть 301, а в заголовке Location: — точный конечный URL. Чтобы увидеть всю цепочку, пройдите по ней:
curl -sIL https://example.com/old-page/ | grep -E '^(HTTP|Location)'
Больше одной строки HTTP/1.1 301 означает, что вы построили цепочку. Больше примерно пяти — что вы построили петлю, и curl остановится и сам вам об этом сообщит.
Пока не уверены — используйте 302. Ответ 302 не кэшируется так же, поэтому ошибка откатывается за секунды. Переключайтесь на 301, когда curl покажет ровно тот адрес, который вы задумали. Это ничего не стоит и спасло больше сайтов, чем любая другая привычка из этой статьи.
Если правило вообще будто бы ничего не делает, убедитесь, что Apache вообще читает файл. AllowOverride None на каталоге заставляет Apache полностью игнорировать .htaccess, а nginx игнорирует его всегда — проверьте заголовок Server: через curl -I, прежде чем отлаживать регулярки. Чтобы собрать блок с правильным синтаксисом под вашу версию Apache и путь установки, воспользуйтесь генератором .htaccess для WordPress.
Когда .htaccess вообще не нужен
Если редиректов немного и ими должны управлять сами редакторы, плагин редиректов, хранящий правила в базе, — инструмент лучше: он переживает переезд на другой хостинг и не требует SSH. Размен тут реальный: чтобы отдать редирект, приходится поднимать PHP, а это медленнее, чем прямой ответ Apache.
Используйте .htaccess для структурных, постоянных переносов — смена домена, переименование раздела, принудительный HTTPS или www. Плагин — для разовых редакционных редиректов. Совмещать оба подхода нормально, пока вы понимаете, какой слой владеет каким URL: редирект, заданный в двух местах, — это баг, который ждёт своего неудачного дня.
FAQ
Вопросы
Куда добавлять свои редиректы в файле .htaccess WordPress?
Выше строки # BEGIN WordPress, но ни в коем случае не между маркерами. WordPress перегенерирует всё, что стоит внутри этих маркеров, каждый раз, когда кто-то сохраняет страницу «Постоянные ссылки», поэтому правило внутри блока удаляется без предупреждения. Расположение важно и для порядка: блок WordPress заканчивается общим правилом, которое отправляет все несовпавшие запросы в index.php.
Что использовать для 301: Redirect, RedirectMatch или RewriteRule?
Redirect — когда есть один конкретный путь, RedirectMatch — когда один шаблон покрывает много URL, RewriteRule — когда редирект зависит от условия: хоста, протокола или строки запроса. Redirect и RedirectMatch относятся к mod_alias и проще. RewriteRule относится к mod_rewrite и единственный умеет проверять условия.
Почему мой редирект в .htaccess зацикливается?
Обычно потому, что адрес назначения по-прежнему подходит под то же правило, которое туда и отправило посетителя, — и правило срабатывает снова и снова. Вторая частая причина: принудительный HTTPS за балансировщиком или CDN, когда сервер видит обычный HTTP на каждом запросе, хотя посетитель уже на HTTPS. В этом случае проверяйте заголовок X-Forwarded-Proto.
Имеет ли значение слеш в конце адреса при редиректе в WordPress?
Да. Канонический редирект WordPress добавляет слеш в конец большинства постоянных ссылок, поэтому 301 на адрес без слеша даёт два перехода вместо одного. Цепочки редиректов всё ещё передают сигналы ранжирования, но тратят краулинговый бюджет и замедляют посетителя. Указывайте точный конечный URL, который отдаёт WordPress, вместе со слешем.
Как проверить редирект в .htaccess, не сломав сайт?
Запустите curl с флагом «только заголовки» по старому адресу и прочитайте код ответа и заголовок Location, прежде чем доверять браузеру. Браузеры агрессивно кэшируют ответы 301 и покажут вам устаревший результат. Сначала сделайте копию рабочего файла: синтаксическая ошибка в .htaccess возвращает 500 на всех страницах, включая wp-admin.
Почему после добавления редиректа весь сайт отдаёт ошибку 500?
Одна некорректная директива в .htaccess кладёт все URL внутри этого каталога, включая админку. Обычные причины: незакрытая обёртка IfModule, отсутствующая строка RewriteEngine On или смешанный в одном файле синтаксис доступа Apache 2.2 и 2.4. Удалите последний добавленный блок и перезагрузите страницу, чтобы это подтвердить.