Если вы меняете структуру постоянных ссылок WordPress, каждый старый URL перестаёт работать, пока что-то не ответит на него редиректом 301 на новый адрес. В общем случае WordPress этого не сделает: он перенаправляет лишь то, что может вычислить сам, например изменённый слаг или адрес вида ?p=123, а на остальное отвечает 404. Безопасный порядок короток. Выгрузите список текущих URL, смените структуру, добавьте одно правило, которое переводит старый шаблон в новый, проверьте через curl -I и только потом доверяйте результату. Остальная часть статьи - про коды ответа, правила для .htaccess в стиле Apache и для nginx и про несколько причин, на которые приходится почти каждая петля редиректов.
Что такое 301 и когда нужен другой код#
Редирект - это обычный HTTP-ответ с кодом статуса и заголовком Location. Браузер или поисковый робот читает заголовок и запрашивает новый адрес. Код статуса подсказывает, как долго помнить этот ответ.
| Код | Значение | Когда использовать |
|---|---|---|
301 | Перемещён навсегда | URL, у которого теперь постоянный новый адрес |
302 | Найден (временно) | Проверка правила, краткие кампании |
307 | Временно, метод сохраняется | POST, который должен остаться POST |
308 | Навсегда, метод сохраняется | Строгий двойник 301 |
Для постоянных ссылок правильный ответ - 301. Он сообщает поисковым системам, что вес старого URL нужно перенести на новый, а браузеры его кэшируют. Именно кэширование и опасно: неверный 301 остаётся в браузерах посетителей, когда вы уже исправили сервер, а до этих браузеров вам не добраться. Поэтому каждое новое правило сначала пишите как 302, проверяйте и меняйте одно слово на 301, когда всё верно. В статье о редиректах между www и основным доменом та же мысль разобрана для редиректа хоста, и всё ниже предполагает, что вы знаете, как этот уровень должен работать.
301 - это и не вечная аренда старого адреса. Держите правило не меньше года, а лучше столько, сколько живёт сайт. Старые ссылки на чужих сайтах и в чужих закладках не истекают, когда ваш поисковый индекс обновился.
Что WordPress перенаправляет сам, а что нет#
В WordPress есть два встроенных поведения, похожих на редиректы, и важно знать, где они заканчиваются.
Первое - каноническое перенаправление (redirect_canonical()). Если кто-то запрашивает /?p=123, а у записи 123 есть красивая постоянная ссылка, WordPress отправляет его на красивую через 301. Он также добавляет или убирает завершающий слеш в соответствии с вашей структурой и перенаправляет на хост из параметра home. Поэтому вопрос о том, какой уровень отвечает за редиректы, так важен, и поэтому два уровня, борющиеся друг с другом, - обычное начало петли.
Второе - механизм старых слагов. Когда вы меняете слаг записи в редакторе, WordPress запоминает прежний слаг в метаданных записи (_wp_old_slug) и перенаправляет с него. Есть и запасной вариант для 404, который угадывает запись по последней части URL, так что /2023/04/my-post/ иногда сам оказывается на /my-post/ без вашего правила.
Не стройте миграцию на этом. Угадывание работает только при уникальном слаге, только для записей и только тогда, когда WordPress решит попытаться. Оно ничего не делает для базы рубрик, изменений иерархии страниц, пользовательских типов записей, адресов вложений и всех случаев, когда изменился сам слаг. Считайте это бонусом для одного забытого URL, а не планом.
Перед тем как менять настройку постоянных ссылок#
Постоянные ссылки находятся в разделе Settings, затем Permalinks. Варианты: Plain (/?p=123), Day and name, Month and name, Numeric, Post name и Custom Structure. Все они строятся из тегов вроде %year%, %monthnum%, %postname% и %category%. Post name (/%postname%/) - то, к чему приходит большинство сайтов: коротко, читаемо и без дат, из-за которых хорошая статья выглядит старой.
Изменение настройки мгновенно и действует на весь сайт. Старые URL не переименовываются, их просто перестают создавать, поэтому подготовьтесь заранее.
- Сделайте резервную копию. Файлы и база данных. Сама смена постоянных ссылок трогает немногое, но дальше вы будете править
.htaccessили конфигурацию сервера, а планка для копии задана в статье о backup, который действительно восстанавливается. - Запишите все текущие URL. Этот список вы будете проверять после изменения. С WP-CLI:
$ wp post list --post_type=post,page --post_status=publish \ --fields=ID,post_name,url --format=csv > old-urls.csv$ wc -l old-urls.csv- Проверьте текущую структуру, чтобы точно знать, что вы заменяете:
$ wp option get permalink_structure/%year%/%monthnum%/%postname%/- Выясните, какие результаты поиска важны. Откройте Search Console или вашу аналитику и отметьте десять-двадцать URL, которые приводят трафик. Именно их нужно проверять вручную.
Если доступа к оболочке нет, а на хостинге с панелью это обычная ситуация, пропустите WP-CLI и выгрузите список через плагин или скопируйте его из карты сайта. Принцип тот же: список старых адресов должен лежать в файле до изменения, потому что потом спросить WordPress, какими были старые URL, уже не получится.
Смена структуры#
Измените настройку в Settings, затем Permalinks, выберите новую структуру и сохраните. В WP-CLI то же самое выглядит так:
$ wp rewrite structure '/%postname%/'$ wp rewrite flush$ wp rewrite list | headСохранение страницы заново создаёт правила перезаписи. На сервере, который читает .htaccess, при этом также переписывается блок WordPress в этом файле, если файл доступен для записи. Если нет, WordPress покажет блок, чтобы вы вставили его сами.
Одно предупреждение: если у вас есть пользовательский тип записей или магазин WooCommerce, проверьте их базовые настройки отдельно. У них свои слаги, и ваше общее правило не должно их поглотить.
Правило, переводящее старое в новое#
Цель - одно правило, которое захватывает часть, которую нужно сохранить, и отбрасывает остальное. Для перехода с /2024/05/my-post/ на /my-post/ шаблон читается как "четыре цифры, слеш, две цифры, слеш, слаг".
На серверах в стиле Apache редирект помещают в .htaccess выше блока # BEGIN WordPress: собственное правило WordPress отправляет всё, что не является файлом, в index.php, и иначе оно сработало бы первым:
<IfModule mod_rewrite.c>RewriteEngine OnRewriteRule ^[0-9]{4}/[0-9]{2}/([^/]+)/?$ /$1/ [R=302,L]</IfModule># BEGIN WordPress<IfModule mod_rewrite.c>RewriteEngine OnRewriteBase /RewriteRule ^index\.php$ - [L]RewriteCond %{REQUEST_FILENAME} !-fRewriteCond %{REQUEST_FILENAME} !-dRewriteRule . /index.php [L]</IfModule># END WordPressВедущего слеша в шаблоне нет намеренно: в .htaccess правило видит путь без префикса каталога. В основной конфигурации сервера то же правило требует ^/ в начале. Держите это правило в форме RewriteRule. Смешивание Redirect или RedirectMatch (это другой модуль Apache) с RewriteRule в одном файле даёт результат, зависящий от порядка модулей, и удивляет людей.
В nginx та же задача решается через location с регулярным выражением. Фигурные скобки требуют заключить шаблон в кавычки:
location ~ "^/[0-9]{4}/[0-9]{2}/([^/]+)/?$" { return 302 /$1/;}Поместите его в блок server, рядом с имеющимся location /, в котором лежит try_files $uri $uri/ /index.php?$args;. Регулярные location проверяются раньше обычного совпадения по префиксу, поэтому оно выигрывает. После проверки замените 302 на 301; в nginx rewrite ... permanent; делает то же, что return 301, а return проще и предпочтительнее.
Ни одно из этих правил в таком виде не полно, и пробелы стоит знать:
- Архивы по датам - это не записи. У
/2024/05/нет слага, и он не совпадает, что правильно. Но/2024/05/feed/по форме похож на запись и был бы перенаправлен на/feed/. Добавьте исключение дляfeed,pageи других зарезервированных слов вашего сайта либо перечислите их первыми и остановите обработку. - Страницы с родителями. Страница
/services/hosting/не подходит под шаблон с датой, поэтому это правило её не затрагивает, и если вы меняете только структуру записей, она продолжает работать. - База рубрик. Перенос
/category/news/в/news/- отдельное правило, и ошибиться в нём легко, потому что оно конфликтует со слагами страниц. Проверьте конфликты, прежде чем делать это. - Строки запроса. Оба правила выше отбрасывают строку запроса при перенаправлении, а для переноса постоянных ссылок это почти всегда то, что нужно. Если вам нужно, чтобы параметры
?utm_сохранялись, скажите об этом явно:[R=301,L,QSA]в Apache;returnв nginx не добавляет запрос, пока вы не допишете$is_args$args.
Много отдельных URL: карты и плагин Redirection#
Шаблон работает, когда изменение систематично. Он не работает, когда изменились сами слаги или когда двадцать старых статей слили в пять. Тогда нужен явный список: одна строка - один старый путь и один новый.
Для списка из пары десятков хватит обычных правил. Для сотен чистое решение в nginx - map: он живёт в блоке http и ищется за постоянное время.
map $request_uri $moved_to { default ""; /old-hosting-guide/ /guides/hosting/; /2023/11/vps-vs-shared/ /vps-vs-shared-hosting/;}server { # inside the existing server block if ($moved_to) { return 301 $moved_to; }}В nginx if ограничен, и это одно из немногих безопасных применений, поскольку внутри только return. В Apache есть RewriteMap, но он допускается только в основной конфигурации сервера и никогда в .htaccess. На хостинге общего типа доступа к ней у вас не будет, поэтому практический выбор - много строк RewriteRule или плагин.
Самый распространённый плагин - Redirection: он хранит список в базе данных, ведёт журнал 404, по которому можно найти забытые URL, и умеет импортировать CSV. Он работает внутри PHP, поэтому каждый перенаправленный запрос стоит полной загрузки WordPress, тогда как серверное правило отвечает за микросекунды. Для нескольких тысяч редиректов это приемлемо; для сайта с высокой нагрузкой используйте плагин, чтобы найти URL, а потом перенесите правила на сервер. В материале о скорости и кэшировании WordPress объяснено, почему полная загрузка WordPress на каждый запрос - самая дорогая часть.
.htaccess или nginx: что у вас?#
Это важно, потому что они работают по-разному, а веб-сервер вашего хостинга не стоит предполагать.
- Apache читает
.htaccessпри каждом запросе в каждом каталоге, через который проходит путь. Это медленнее центральной конфигурации, зато правила можно менять без перезапуска, и их может править плагин. Работает только если сервер это разрешает (AllowOverride), что на большинстве хостингов так. - LiteSpeed и OpenLiteSpeed тоже читают
.htaccessс совместимым синтаксисом для распространённых директив. - nginx не имеет
.htaccess. Правила лежат в конфигурации сервера, а изменения требуют перезагрузки. Файл.htaccessв папке WordPress на сервере nginx не читает ничто, и это классическая причина жалобы "мой редирект ничего не делает".
Чтобы выяснить, посмотрите заголовки ответа:
$ curl -sI https://example.com/ | grep -i '^server'server: nginxЭтот заголовок можно скрыть или изменить, поэтому отсутствующее или общее значение ответом не является. Другой признак - файловый менеджер: если в корне сайта есть .htaccess, который WordPress переписал при сохранении постоянных ссылок, значит, что-то его читает. Если при сохранении постоянных ссылок появляется сообщение, что WordPress не смог обновить .htaccess, то либо сервер его игнорирует, либо файл только для чтения.
Панель RE:NODE здесь ничего не подскажет, а эта статья гадать не будет. Посмотрите на реальный заголовок Server вашего сайта и на то, что лежит в корне ваших файлов в файловом менеджере, и следуйте тому разделу, который подходит. Если конфигурация веб-сервера вам недоступна для правки, ответ такой: путь через .htaccess, если сервер его читает; плагин Redirection, если не читает; и обращение в поддержку, если нужно серверное правило, которое вы не можете написать сами.
Петли редиректов: причины по убыванию вероятности#
ERR_TOO_MANY_REDIRECTS в Chrome, "The page isn't redirecting properly" в Firefox. Петля означает, что две вещи не согласны, каким должен быть канонический URL, и каждая отправляет посетителя к версии другой. Найдите разногласие и уберите одну из сторон.
Сначала посмотрите на цепочку, а не гадайте. curl по умолчанию ничего не отслеживает, поэтому скажите ему следовать и ограничьте число переходов:
$ curl -sIL --max-redirs 8 https://example.com/some-post/ \ | grep -i -E '^(HTTP|location)'Читайте строки Location по порядку. В петле два одних и тех же адреса чередуются. Затем сверьте увиденное с этими причинами.
HTTPS за прокси. Самая частая причина на современном хостинге. TLS заканчивается на обратном прокси, который передаёт веб-серверу обычный HTTP. Правило или плагин видит http://, перенаправляет на https://, прокси снова снимает TLS, и цикл повторяется. Решение - заставить WordPress доверять заголовку, который выставляет прокси; в wp-config.php это выглядит так, и вставлять нужно выше строки, где сказано прекратить правку:
if ( isset( $_SERVER['HTTP_X_FORWARDED_PROTO'] ) && 'https' === $_SERVER['HTTP_X_FORWARDED_PROTO'] ) { $_SERVER['HTTPS'] = 'on';}Убедитесь, что ваш прокси действительно отправляет X-Forwarded-Proto, прежде чем на него полагаться; на RE:NODE слот прокси описан как передающий адрес посетителя в X-Forwarded-For, поэтому проверьте заголовок протокола, а не предполагайте. Статья о том, что делает обратный прокси, объясняет эти заголовки, а материал о HTTPS и Let's Encrypt - где находится сертификат.
Адрес сайта расходится с правилом. siteurl и home в базе данных содержат http://example.com, а серверное правило принудительно ведёт на https://www.example.com. WordPress перенаправляет в одну сторону, сервер в другую. Посмотрите, что сохранено:
$ wp option get home$ wp option get siteurlЕсли до панели управления не добраться, закрепите оба значения в wp-config.php: они переопределяют значения из базы, пока вы не исправите их как следует:
define( 'WP_HOME', 'https://example.com' );define( 'WP_SITEURL', 'https://example.com' );Борьба за завершающий слеш. WordPress добавляет завершающий слеш к красивым ссылкам. Серверное правило или плагин, который его убирает, отправляет /post/ на /post, а WordPress тут же возвращает обратно. Выберите слеш. Собственное соглашение WordPress - оставлять его, поэтому уберите правило, которое его срезает.
Два уровня редиректов. Плагин безопасности, плагин кэширования, плагин редиректов, файл .htaccess и прокси хостинга могут каждый принудительно включать HTTPS или канонический хост. По отдельности каждый прав, а вместе они дают петлю. Отключайте их по одному снаружи: переименуйте wp-content/plugins/the-plugin через файловый менеджер или выполните wp plugin deactivate --all и включайте обратно, пока петля не вернётся.
Правило, совпадающее со своим же результатом. Самая простая петля, которая встречается в правилах, написанных вручную: слишком свободный шаблон вроде ^(.*)$, перенаправляющий каждый путь на чуть другой путь, совпадёт со своим результатом и сработает снова. Привязывайте шаблоны жёстко и следите, чтобы результат не мог совпасть с входом. В правиле для дат выше цель /my-post/ не содержит цифр, поэтому снова совпасть не может.
Кэшированный редирект. После устранения причины браузер продолжает воспроизводить закэшированный 301. Проверяйте в приватном окне или через curl, у которого кэша нет. Если вы проверяете для реальных посетителей, помните, что CDN или кэш страниц перед сайтом тоже мог закэшировать редирект, так что очистите его. Пустая страница или ошибка 500 после правки .htaccess - другая проблема, она разобрана в статье о белом экране и ошибках 500 в WordPress.
Проверка каждого редиректа#
Проверяйте через curl -I, а не кликами, потому что curl показывает код статуса и адрес назначения без кэша браузера и автоисправлений. Прогоните его по списку, который сохранили до изменения:
$ while read -r path; do printf '%s -> ' "$path" curl -s -o /dev/null -w '%{http_code} %{redirect_url}\n' \ "https://example.com$path" done < old-paths.txtДля каждого старого URL нужен один 301 (или 302 на время проверки) на новый URL, а затем 200 по адресу назначения. Всё, что показывает два перехода, стоит исправить: первый редирект ведёт посетителей на URL, который сам перенаправляет, а это тратит время и размывает сигнал. Всё, что показывает 404, - URL, который ваше правило пропустило. Всё, что показывает 301 на главную страницу, - ленивое правило, выбрасывающее смысл ссылки; отправляйте каждую старую страницу к её настоящему преемнику.
Когда вывод чист, замените 302 на 301, перезагрузите сервер, где это требуется, и прогоните список ещё раз.
После этого: ссылки, карта сайта и поиск#
Редиректы обрабатывают тех, кто приходит снаружи. Всё, что вы контролируете, должно перестать на них полагаться.
- Обновите внутренние ссылки. Записи, ссылающиеся друг на друга полными старыми URL, теперь получают редирект при каждом клике. Сначала посмотрите результат:
wp search-replace 'example.com/2024/05/' 'example.com/' --dry-run, прочтите число замен и запускайте без флага только после резервной копии. Замену по шаблону в сериализованных данных всегда делайте через WP-CLI, а не сырым SQLUPDATE. В статье о переносе WordPress на новый хостинг объяснено, почему. - Пересоздайте карту сайта, чтобы в ней были только новые URL, и отправьте её заново в Search Console.
- Следите за 404. Несколько недель смотрите в журнал веб-сервера или в монитор 404 на предмет пропущенных старых URL.
- Ожидайте просадки. Поисковым системам нужны дни или недели, чтобы обработать такой перенос. Временное колебание, которое затем успокаивается, - норма; обвал, который не восстанавливается, - признак правила, перенаправляющего не туда.
На RE:NODE сайт WordPress на линейке web получает те же файловый менеджер и SFTP, что и любой другой тариф, и этого достаточно, чтобы править .htaccess или wp-config.php и сделать копию перед этим.
FAQ#
Повредит ли смена постоянных ссылок позициям в поиске?
Нет, если каждый старый URL отвечает одним 301 на прямую замену. Ожидайте недолгого колебания, пока роботы заново обрабатывают сайт. Позиции теряются, когда старые URL отдают 404, ведут на главную или проходят через несколько переходов.
Какой редирект использовать для смены постоянных ссылок: 301 или 302?
302 на время проверки, потому что неверный 301 надолго остаётся в кэше браузеров, а затем 301 для итогового правила. Смена постоянных ссылок необратима, поэтому конечное состояние всегда 301.
Почему мой редирект в .htaccess ничего не делает?
Обычно одна из трёх причин: сервер - nginx и не читает .htaccess, отключён AllowOverride или правило стоит ниже блока WordPress и до него дело не доходит. Проверьте заголовок Server через curl -I и переместите правило выше # BEGIN WordPress.
Можно ли делать редиректы без плагина?
Да, и для систематического шаблона так и стоит делать. Серверное правило отвечает до запуска PHP. Плагины оправданы, когда у вас сотни отдельных URL или нужен журнал 404, с которым можно работать.
Как долго держать редиректы?
Не меньше года, а лучше бессрочно. Они почти ничего не стоят, а внешние ссылки и закладки на старые URL не исчезают, когда поисковые системы обновляются.
Что означает ERR_TOO_MANY_REDIRECTS?
Браузер прошёл больше редиректов, чем готов принять, обычно около двадцати, потому что два правила гоняют посетителя туда и обратно. Выполните curl -sIL --max-redirs 8 для этого URL, прочтите строки Location и уберите одну сторону разногласия.


