8 мин чтенияТуториалы

301-редирект в .htaccess: правила, порядок, строки запроса

301-редирект в .htaccess - это одна строка mod_alias. Вот эта строка, когда вместо неё нужен RewriteRule, и почему порядок важнее позиции строки в файле.

Marius Voß
DevRel · edge infra
301-редирект в htaccess показан как одна строка mod_alias рядом с правилом mod_rewrite, с порядком выполнения между ними

301-редирект в .htaccess - это одна строка:

Redirect 301 /old-page https://example.com/new-page

Сохраните это в файле .htaccess в корне документов - и оно заработает немедленно. Apache читает файл при каждом запросе, так что не нужны ни перезапуск, ни деплой. Директива приходит из mod_alias, который присутствует практически на любой установке Apache, и отправляет 301 Moved Permanently с заголовком Location. Это вся задача.

Почти всё, что идёт не так с редиректами Apache, происходит после этого момента: тянутся за RewriteRule, когда достаточно Redirect, смешивают два модуля в одном файле и получают порядок, которого никто не ожидал, или теряют строку запроса по дороге. Эта статья разбирает четыре правила, которые стоит запомнить, ловушку порядка выполнения, из-за которой правильно выглядящие файлы ведут себя неправильно, и как проверить редирект без того, чтобы браузер вас обманывал. Более широкую картину того, где может жить редирект, смотрите в статье как перенаправить URL: шесть способов, и когда какой из них уместен.

Одна строка, которая покрывает большинство редиректов

Redirect принимает статус, путь для совпадения и цель. Цель может быть полным URL или путём на том же хосте:

Redirect 301 /old-page /new-page
Redirect 301 /shop https://shop.example.com/
RedirectMatch 301 ^/blog/([0-9]{4})/(.*)$ /articles/$2

Стоит знать о двух особенностях, прежде чем использовать это. Во-первых, собственное руководство Apache прямо говорит, что это правильный инструмент: "Такую простую переадресацию одного URL или класса URL куда-то ещё следует выполнять с помощью этих директив, а не RewriteRule."

Во-вторых, и это удивляет людей: "Помните, что Redirect сохраняет информацию о пути. То есть редирект для URL /one также перенаправит все URL под ним, такие как /one/two.html и /one/three/four.html." Если вам нужен только точный путь, RedirectMatch 301 ^/one$ фиксирует его. Иначе вы только что перенаправили целое поддерево, а это иногда именно то, что вы хотели, а иногда - очень запутанное утро.

RedirectMatch - это версия с регулярными выражениями, и она покрывает большую часть того, для чего люди открывают mod_rewrite. Захваченные группы попадают в $1, $2 и так далее, так что переименование директории или смена схемы URL на основе дат укладывается в одну строку.

Когда вам действительно нужен RewriteRule

Обращайтесь к mod_rewrite, когда решение зависит от чего-то, кроме пути. Хост, строка запроса, метод запроса, куки и user agent - всё это видно RewriteCond и невидимо для Redirect:

RewriteEngine On
RewriteCond %{QUERY_STRING} (^|&)ref=oldpartner(&|$)
RewriteRule ^landing$ /partners/oldpartner? [R=301,L]

Есть одна ловушка в контексте директории, которая объясняет огромную долю скопированных правил, вообще ничего не делающих. Apache удаляет префикс директории перед сопоставлением, так что шаблон никогда не видит начальный слеш: "Удалённый префикс всегда заканчивается слешем, что означает, что сопоставление происходит со строкой, которая никогда не имеет начального слеша. Поэтому шаблон с ^/ никогда не совпадает в контексте директории."

Именно поэтому RewriteRule ^/old$ /new [R=301] работает, когда кто-то вставляет это в виртуальный хост, и незаметно не работает в .htaccess. Убрите слеш: ^old$. Когда ваша подстановка - относительный путь, а правило живёт в подкаталоге, вам может также понадобиться RewriteBase, чтобы указать Apache, относительно чего строятся пути.

Ловушка порядка выполнения

Это та ловушка, которая обходится людям в потерянный день. Позиция строки в файле не решает, какой модуль запускается первым.

Apache документирует это прямо: "Если вы смешиваете Redirect и RewriteRule в одном контексте, учтите, что порядок их выполнения зависит от того, где они расположены. В контексте сервера/виртуального хоста первым запускается mod_rewrite; в контексте директории (.htaccess) первым запускается mod_alias."

Прочитайте это дважды, потому что следствие противоречит интуиции. В файле .htaccess Redirect в самом низу файла побеждает RewriteRule в самом верху. Перенесите те же самые правила в виртуальный хост, и победитель меняется. Флаг [L] тоже не спасает: он означает последнее правило в этом проходе mod_rewrite, а не последнее правило в файле, и у него нет власти над другим модулем.

Практическое правило, которому я следую: один модуль на файл. Если проекту где-либо нужны условия, делайте все его редиректы через mod_rewrite и удаляйте строки Redirect. Именно из смешанных файлов возникают баг-репорты вроде "редирект работает на staging, но не на продакшене", потому что в двух средах правила оказываются в разных контекстах.

Порядок выполнения mod_alias и mod_rewrite показывает, что в файле htaccess mod_alias запускается первым независимо от позиции строки

Строки запроса: сохранены, заменены или стёрты

Маркетинговые ссылки живут и умирают своими строками запроса, так что эту таблицу стоит держать перед глазами. Всё здесь - задокументированное поведение, а не народные легенды.

Что вы пишетеИсходная строка запросаПримечания
Redirect 301 /a /bПередаётся дальшеmod_alias добавляет её сам
RedirectMatch 301 ^/a$ /bПередаётся дальшеТот же модуль, то же поведение
RewriteRule ^a$ /b [R=301]Проходит без измененийЗадокументированное поведение по умолчанию
RewriteRule ^a$ /b?src=x [R=301]Заменяется вашейВаши параметры побеждают
RewriteRule ^a$ /b?src=x [R=301,QSA]Объединяется с вашейQSA добавляет исходную
RewriteRule ^a$ /b? [R=301]СтираетсяПустой ? очищает её

Формулировка Apache о поведении по умолчанию: "По умолчанию строка запроса передаётся без изменений." А о флагах: [QSA] "добавляет любую строку запроса из исходного URL запроса к любой строке запроса, созданной в цели переписывания", а [QSD] отбрасывает входящую. Если вы перенаправляете на абсолютный URI, строка запроса переносится вместе, если вы не запросили [QSD].

Отказ, который это предотвращает, тихий и дорогой. Правило, заменяющее строку запроса, срезает по дороге utm_source и utm_campaign, ваша аналитика приписывает сессию прямому трафику, и ничего не выдаёт ошибки. Никто не замечает, пока кто-то не спросит, почему весенняя кампания не получила ни одной заслуги. Статья UTM-параметры не отображаются в GA4 разбирает диагностику со стороны аналитики.

Если вы обнаруживаете, что поддерживаете десятки кампанийных редиректов в конфигурационном файле сервера, это сигнал, а не рутина. Правила на сервере требуют деплоя, ревью конфигурации Apache и кого-то с доступом по shell. Переместите кампанийные ссылки на короткие ссылки, которые вы можете редактировать сами и оставьте .htaccess для структурных редиректов, в которых он хорош.

HTTPS и www за один переход, а не за два

Самый копируемый сниппет в интернете делает это в двух блоках правил, а значит, посетитель, попавший на http://example.com/page, получает редирект дважды: один раз для добавления TLS, другой раз для добавления www. Два перехода, два круговых обращения, и на каждом - чуть более слабый сигнал.

Одно правило, два условия, один переход:

RewriteEngine On
RewriteCond %{HTTPS} !=on [OR]
RewriteCond %{HTTP_HOST} !^www\.example\.com$ [NC]
RewriteRule ^ https://www.example.com%{REQUEST_URI} [R=301,L]

За CDN или балансировщиком нагрузки %{HTTPS} на origin-сервере обычно off, даже когда посетитель на HTTPS, потому что TLS завершается выше по цепочке. Проверяйте вместо этого %{HTTP:X-Forwarded-Proto}:

RewriteCond %{HTTP:X-Forwarded-Proto} !https

Ошибитесь здесь - и вы построите бесконечный цикл редиректов: прокси отправляет HTTPS, origin считает, что это HTTP, перенаправляет на HTTPS, и всё идёт по кругу, пока браузер не сдастся. Статья как исправить петлю редиректов разбирает диагностику по заголовкам ответа.

Два отдельных правила htaccess создают цепочку редиректов из двух переходов, в сравнении с одним правилом, которое достигает канонического HTTPS www URL за один переход

Почему это не работает

В том порядке, в котором я их проверяю:

  1. AllowOverride равен None. Документация Apache описывает поведение по умолчанию: "Это означает, что файлы .htaccess полностью игнорируются, если вы явно не включили их для директории." Проверьте это, поставив намеренный мусор в первую строку. Отсутствие ошибки 500 означает, что ваш файл вообще не читается, и каждое правило в нём - просто украшение.
  2. Файл лежит не там или неправильно назван. Он должен называться .htaccess, с точкой в начале, и лежать в директории, на которую отображается запрос. Редакторы, которые "заботливо" сохраняют его как htaccess.txt, - повторяющаяся причина.
  3. mod_rewrite не загружен. Redirect работает, RewriteRule незаметно не работает, из-за чего люди час разглядывают своё регулярное выражение.
  4. Ваш браузер закешировал старый 301. Chrome и Firefox оба агрессивно кешируют постоянные редиректы, в привязке к профилю браузера, так что только что задеплоенное исправление невидимо для вас и прекрасно работает у всех остальных. Во время разработки используйте R=302, а переключайтесь на R=301, когда правило готово.
  5. Правило совпадает со своей же целью. Классический пример - RewriteRule ^(.*)$ /index.php/$1 без защиты. Добавьте условие, которое исключает целевой путь.

Проверяйте через curl, а не через браузер

Одна команда сообщает вам статус, цель и сколько переходов потребовалось:

curl -sIL https://example.com/old-page | grep -iE '^HTTP|^location'

Читайте вывод как последовательность. Две строки HTTP/2 301 перед 200 означают два перехода, а каждый переход - это реальное круговое обращение для реального посетителя в мобильной сети. Документация Google по редиректам считает постоянный редирект самым сильным сигналом для консолидации URL, а добраться туда за один шаг строго лучше, чем за три. Наш чекер ссылок выводит ту же цепочку в браузере, а статья 301 против 302 редиректов разбирает, какой статус отправлять, если вы не уверены.

Проверяйте пути, которые действительно важны, а не только тот, что вы только что написали: корень, глубокий путь, путь со строкой запроса и саму цель. Последнее ловит циклы раньше, чем это сделают ваши посетители.

Когда не стоит использовать .htaccess вообще

Собственная позиция Apache однозначна. "Если у вас есть доступ к основному файлу конфигурации сервера, вам следует размещать всю конфигурацию там, а не в файлах .htaccess," потому что разбор при каждом запросе стоит реальной работы: "разрешение файлов .htaccess вызывает снижение производительности, независимо от того, используете вы их на самом деле или нет." На shared-хостинге у вас нет выбора. На сервере, который вы контролируете, основная конфигурация - лучший дом для всего структурного.

Есть и вторая причина держать правила подальше от файла целиком, и она не имеет ничего общего с производительностью. Редиректы, которые оказываются в печати, в QR-коде или на чужой слайд-презентации, должны переживать ваш веб-сервер, миграцию CMS и, возможно, вашего хостинг-провайдера. Правило в .htaccess находится на расстоянии одного неосторожного деплоя от исчезновения, а никто не сообщает о мёртвой напечатанной ссылке, пока кампания не закончится. Структурные редиректы должны жить на сервере; кампанийные и печатные ссылки должны жить там, где вы можете редактировать их за секунды и измерять без грепа логов доступа.

Читайте цикл опорных статей

Эта статья входит в кластер туториалов. Для полной карты статья типы редиректов охватывает все коды статуса и клиентские методы, а как перенаправить URL: шесть способов, и когда какой из них уместен охватывает шесть мест, где может жить редирект.

Связанные статьи в блоге

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

Как создать 301-редирект в .htaccess?

Добавьте одну строку в файл .htaccess в корне документов: Redirect 301 /old-page https://example.com/new-page. Apache читает .htaccess при каждом запросе, так что редирект начинает работать в момент сохранения файла. Без перезапуска, без деплоя. Эта директива приходит из mod_alias, который включён практически на любой установке Apache.

В чём разница между Redirect и RewriteRule?

Redirect и RedirectMatch приходят из mod_alias и делают одну вещь: отправляют код статуса и заголовок Location. RewriteRule приходит из mod_rewrite и может проверить хост, строку запроса, куки или user agent, прежде чем принять решение. Собственная документация Apache говорит, что для простой переадресации нужно использовать mod_alias, а не RewriteRule, и относится к mod_rewrite как к крайнему средству.

Почему мой редирект в .htaccess не работает?

Пять причин покрывают почти всё: AllowOverride равен None, и файл полностью игнорируется; файл не в корне документов или неправильно назван; mod_rewrite не загружен; браузер закешировал старый 301 и больше не спрашивает сервер; или правило совпадает со своей же целью и уходит в цикл. Проверяйте через curl, а не через браузер, потому что закешированный 301 делает исправленное правило похожим на сломанное.

Сохраняется ли строка запроса при 301-редиректе в .htaccess?

С Redirect и RedirectMatch - да, она переносится автоматически. С RewriteRule ответ зависит от подстановки: без знака вопроса в ней исходная строка запроса проходит насквозь, знак вопроса с вашими параметрами заменяет её, одинокий знак вопроса на конце стирает её, а флаг QSA объединяет обе. Ошибка здесь незаметно теряет UTM-параметры.

Как перенаправить HTTP на HTTPS и non-www на www за один переход?

Используйте одно правило с двумя условиями, объединёнными через OR, переписывая в канонические схему и хост за один шаг. Два отдельных блока правил создают два редиректа для любого, кто приходит на http:// без www, а каждый лишний переход стоит задержки и размывает сигнал. За прокси или CDN проверяйте %{HTTP:X-Forwarded-Proto} вместо %{HTTPS}, иначе получите цикл.

Замедляет ли .htaccess сайт?

Немного, и это неизбежно. Документация Apache говорит об этом прямо: разрешение файлов .htaccess вызывает снижение производительности независимо от того, используете вы их или нет, потому что httpd ищет файл в каждой директории при каждом запросе. Если у вас есть доступ к основной конфигурации сервера, те же правила должны быть там - они загружаются один раз при старте.

Попробуйте Elido

Вставьте URL - получите короткую ссылку

Без регистрации. Ссылка живёт 30 дней. Зарегистрируйтесь, чтобы оставить её навсегда.

Бесплатно, без регистрации · 2 в день

Попробуйте Elido

URL-сокращатель с хостингом в ЕС: собственные домены, глубокая аналитика, открытый API. Бесплатный тариф - без банковской карты.

Теги
htaccess 301 redirect
apache redirect
mod_rewrite
301 redirect
redirect https
url redirect

Читать дальше