Как сделать 301 редирект через LocalRedirect и CHTTP::SetStatus на 1С-Битрикс

SEO попросил 301 со старого ЧПУ раздела, а в init.php скопировали сниппет с форума: CHTTP::SetStatus и LocalRedirect со строкой статуса. Через неделю в Метрике старый URL жив, curl -I пишет 302 Found. Речь про CMS 1С-Битрикс: Управление сайтом, не Bitrix24. Постоянный hop даёт localredirect битрикс только третьим параметром; ниже вызов, ловушка аргументов и проверка без петли.

LocalRedirect – это функция главного модуля CMS "1С-Битрикс: Управление сайтом": она нормализует URL, вызывает OnBeforeLocalRedirect, выставляет HTTP-статус через CHTTP::SetStatus (по умолчанию 302 Found) и заголовок Location, затем завершает хит. Постоянный 301 задают только третьим параметром с версии 8.5.1; второй параметр – skip_security_check, не код ответа. Не-3xx статусы справка игнорирует и снова отдаёт 302. Сегодня grep-ните вызовы без false, поставьте "301 Moved Permanently" третьим параметром и снимите curl -I.

На практике коллега "починил" дефолт в bitrix/modules/main/tools.php – после обновления главного модуля снова 302. Защита редиректов ругается: строка во втором аргументе truthy и включает skip phishing. Нужна одна строка: LocalRedirect($RESURL, false, "301 Moved Permanently").

Выберите слой: PHP, SEO-модуль или сервер

Выбор слоя редиректа: PHP, SEO-модуль или сервер

Типичная ошибка – писать в PHP сотни статичных пар и параллельно крутить те же URL в админке. Массовые склейки после смены ЧПУ – в SEO-модуле и на сервере, это гайд про настройку 301 редиректов на Битрикс. Сюда заходите, когда адрес зависит от роли, свойства, пустого раздела или пагинации.

Когда Куда Не делать
Адрес зависит от роли, свойства, пустого раздела, пагинации PHP: LocalRedirect или D7 Redirect Дублировать те же URL в админке
Сотни статичных пар old→new после смены ЧПУ SEO-модуль в админке Писать каждую пару в init.php
https, www, слеш, тысячи правил до PHP .htaccess или nginx выше urlrewrite Собирать http через IsHTTPS()
Страницы больше нет, аналога нет 404 через CHTTP::SetStatus, не 301 на /404 301 на /404.php вместо настоящего 404
Логин, POST-форма, временная акция 302 или 303 301: браузер закэширует hop
Вердикт: PHP – когда условие из кода или базы. Статичные пары и доменные склейки – другой слой. ЧПУ urlrewrite не заменяет HTTP 301: это маршрутизация, не "страница навсегда переехала".

Делайте: один слой на пару URL. Не делайте: init.php "на все редиректы", пока не проверили SEO-модуль.

Вызовите LocalRedirect с третьим параметром 301

Схема вызова LocalRedirect с третьим параметром 301

Как сделать редирект из PHP Битрикс с кодом 301: статус идёт третьим аргументом. Сигнатура с 8.5.1: url, skip_security_check=false, status="302 Found". skip_security_check с 8.0.3 – это не код ответа, а обход защиты от фишинга.

LocalRedirect($RESURL, false, "301 Moved Permanently");

В status только коды 3xx. Строка "404 Not Found" третьим аргументом игнорируется – снова 302. Для постоянной склейки GET нужен "301 Moved Permanently". 302 оставьте логину и акциям.

Схема вызова:
условие в init.php → LocalRedirect($url, false, "301 Moved Permanently") → ядро ставит SetStatus и Location → curl -I: один 301
  1. Найдите все LocalRedirect в init.php, компоненте и обработчике.
  2. Поставьте второй аргумент строго false, не строку статуса.
  3. Третьим аргументом передайте 301 Moved Permanently.
  4. Не вызывайте SetStatus перед hop: функция сделает это сама.
  5. Проверьте URL: относительный путь безопаснее http, собранного вручную.
  6. Оставьте skip_security_check=false, иначе откроете внешний редирект.

Делайте: одну строку с тремя аргументами. Не делайте: надеяться, что дефолт "сам станет 301", если SEO "очень просил".

Уберите антипаттерн SetStatus и строку вместо false

Чеклист антипаттернов SetStatus и строки вместо false

Живой пример с форума (пост 10529, фикс 2017): CHTTP::SetStatus("301 Moved Permanently"); LocalRedirect($RESURL, "301 Moved Permanently");. В DevTools и curl всё равно 302 Found. Поисковик не склеивает URL, хотя "редирект уже есть".

LocalRedirect сама вызывает CHTTP::SetStatus($status) перед header("Location") и завершает хит. Предварительный SetStatus – мёртвый код: ядро перезапишет статус третьим параметром, без него подставит "302 Found". Второй аргумент-строка – skip_security_check: непустая строка truthy, защита отключается, статус остаётся дефолтным.

// антипаттерн: снова 302, плюс skip phishing
CHTTP::SetStatus("301 Moved Permanently");
LocalRedirect($RESURL, "301 Moved Permanently");

В реальном проекте правили дефолт в tools.php. После обновления главного модуля hop снова 302. SetStatus – для 404 без Location, не для переезда; это настройка 404 на Битрикс.

Делайте: grep по LocalRedirect($url, "301 и по SetStatus прямо перед hop. Не делайте: патчить bitrix/modules/main/tools.php.

Поставьте 301 через D7 setStatus, не через skipSecurity

В контроллере берите \Bitrix\Main\Engine\Response\Redirect. Дефолт тоже 302. Постоянный код задаёт setStatus, не конструктор. Не копируйте community-пост "с какого релиза появился класс": в выдаче он уже 404, официальная D7-справка версии не ставит.

$response = new \Bitrix\Main\Engine\Response\Redirect($url);
$response->setStatus("301 Moved Permanently");
$response->send();

Ответ можно вернуть из действия без ручного send. Чтобы оборвать хит, после send вызывают Application::end. redirectTo и setSkipSecurity есть в class reference; skipSecurity=true не ставьте: это тот же обход защиты, что второй аргумент LocalRedirect.

  1. Создайте Redirect с целевым путём.
  2. Вызовите setStatus со строкой 301 Moved Permanently.
  3. Верните объект из контроллера или вызовите send и end.
  4. Не копируйте skipSecurity=true из чужого gist.

Результат тот же: один hop 301 и Location. Получите предсказуемый D7-код без правки ядра. Делайте: setStatus рядом с Redirect. Не делайте: смешивать LocalRedirect и send на одном хите.

Проверьте композит, событие OnBeforeLocalRedirect и петлю HTTPS

События OnBeforeLocalRedirect и OnLocalRedirect есть с 6.5.4. Обработчик может подменить URL до Location. Если после деплоя hop уезжает "не туда", grep по AddEventHandler("main", "OnBeforeLocalRedirect". Часто ломается не ваш вызов, а чужой обработчик.

На композите редирект во время AJAX даёт X-Bitrix-Composite: Ajax (error:redirect). Динамика не подгружается. Не редиректите frame-хит: обычный GET должен отдать 301 без error. Включать композит с нуля не нужно; hop – в гайде про композитный сайт на Битрикс.

После SSL за прокси PHP часто видит HTTP. LocalRedirect через CMain::IsHTTPS() собирает http://, nginx кидает на https, браузер пишет ERR_TOO_MANY_REDIRECTS. Лечится X-Forwarded-Proto и https в .settings.php, не третьим 301 "поверх". Тест "внутреннее перенаправление" ждёт 302 Location; Cloudflare может сломать check_localredirect. Это не повод менять SEO-статус.

Проверьте curl -I обычным GET: Location и отсутствие Ajax (error:redirect). Не делайте: дублировать HTTPS-склейку в PHP, если её уже делает nginx.

Закройте чек-лист: curl, корзина и обмен 1С

Критерий готовности: один hop 301 Moved Permanently и Location, без 302 на повторном curl -I без -L. Корзина, /personal/ и 1c_exchange.php остаются 200. На композите нет Ajax (error:redirect). В коде нет LocalRedirect($url, "301 без false.

  1. Запустите curl -I на старый путь: статус 301, не 302 Found.
  2. Сверьте заголовок Location с каноническим URL, без лишнего слеша.
  3. Прогоните curl -I -L --max-redirs 5: не больше одного hop, цель 200.
  4. Откройте корзину и /personal/: не должно уехать 301.
  5. Проверьте ночной обмен: 1c_exchange.php отвечает 200, не редиректом.
  6. Исключите POST-логин: на авторизацию ставьте 302 или 303, 301 кэшируется.
  7. Сбросьте композитный кеш страницы, если видели error:redirect.
$path = $APPLICATION->GetCurPage(false);
if (strpos($path, "/bitrix/admin/1c_exchange.php") === 0
    || strpos($path, "/personal/") === 0
    || strpos($path, "/basket/") === 0) {
    return;
}

Сможете закрыть задачу SEO без сюрприза утром: обмен жив, корзина открывается, hop постоянный. Если условие раскидано по ролям и легаси-обработчикам, обсудите правку на конкретном init.php, а не копируйте ещё один сниппет.

Делайте: стоп-лист служебных путей до LocalRedirect. Не делайте: 301 на POST и на URL, которые должны остаться 200.

Автор: Максим Мольков, разработчик 1С-Битрикс.
Источники: справка LocalRedirect, D7 Redirect::setStatus, MDN 301, композит error:redirect.

Частые вопросы

Чем LocalRedirect отличается от редиректа в модуле SEO?

LocalRedirect срабатывает на хите, когда условие из PHP или базы. Модуль SEO хранит массовые статичные пары без деплоя. Много пар без роли – в админку, не в init.php.

Нужно ли вызывать CHTTP::SetStatus перед LocalRedirect?

Нет. Функция сама вызывает CHTTP::SetStatus($status) перед Location. Предварительный SetStatus ядро перезапишет. SetStatus оставляйте для 404 без Location.

Почему LocalRedirect отдаёт 302?

Дефолт функции – "302 Found". Второй аргумент-строка не статус, а skip_security_check. Не-3xx справка игнорирует и снова ставит 302. Передайте false и "301 Moved Permanently" третьим параметром.

Как проверить 301 на композитном сайте?

Снимите curl -I обычным GET, не AJAX-frame. Нужны 301 и Location. Заголовка X-Bitrix-Composite: Ajax (error:redirect) быть не должно. Затем сбросьте композитный кеш.

Можно ли ставить 301 на авторизацию и POST-формы?

Нет. 301 кэшируется, логин и POST "залипнут". Для входа используйте 302 или 303. 301 – постоянному переезду GET-страницы.

LocalRedirect не работает в проверке системы. Это не 301?

Тест check_localredirect ждёт 302 Found и Location. Cloudflare и прокси ломают hop. Чините инфраструктуру, не SEO-статус. skip_security_check и setSkipSecurity к тесту не привязывайте.

Обсудить вашу задачу

Было полезно?

Поставьте лайк, если статья помогла разобраться.

Поделиться статьёй

Отправьте тому, кому это пригодится.

Telegram ↗ MAX ↗ ВКонтакте ↗ Одноклассники ↗
Все статьи ↗

Как настроить регистрацию и личный кабинет покупателя на 1С-Битрикс?

Структура шаблонов сайта в Битриксе и поиск header footer

Как настроить скидки и промокоды на 1С-Битрикс: правила корзины и купоны