Как работает mod_rewrite
mod_rewrite — модуль Apache, который переписывает URL «на лету»: красивые адреса, редиректы, склейка зеркал, блокировки, режим обслуживания. Этот туториал разбирает синтаксис по частям. Свои правила можно прогнать на конкретном URL в тестере /rewrite/, готовые блоки — в рецептах и генераторе.
Хотите пройти по шагам с упражнениями? — Академия mod_rewrite (5 уроков от первого правила до сборки целого .htaccess).
Что такое mod_rewrite и зачем он
mod_rewrite — модуль Apache, который переписывает URL запроса «на лету», до того как сервер решит, какой файл/скрипт отдавать. С его помощью делают: человекопонятные URL (ЧПУ — /blog/5 вместо /post.php?id=5); постоянные редиректы (301) при переезде/переименовании страниц; склейку зеркал (с www ↔ без www, http → https); блокировки по User-Agent, рефереру, IP; режим обслуживания; запрет доступа к файлам. Правила пишутся в .htaccess (или в конфиге Apache).
Важно: для простых статичных редиректов mod_rewrite не обязателен — Redirect 301 /old /new и RedirectMatch из модуля mod_alias проще и не требуют RewriteEngine. mod_rewrite нужен, когда есть условия (RewriteCond), захваты, или гибкая трансформация пути. Много 301-редиректов из списка удобно сгенерировать в пакетном генераторе.
RewriteEngine On
Без строки RewriteEngine On в .htaccess все RewriteRule/RewriteCond игнорируются — это причина №1 «правила не срабатывают». Ставьте её в начало блока правил; RewriteEngine Off — наоборот, отключает. Тонкость про подкаталоги: правила (RewriteRule) из .htaccess верхнего уровня по умолчанию не действуют в .htaccess подкаталога (об этом — в разделе «частые ошибки» и про RewriteOptions Inherit); поэтому в каждом .htaccess, где есть свои rewrite-правила, RewriteEngine On принято указывать заново.
RewriteRule: паттерн → подстановка → флаги
Синтаксис: RewriteRule Паттерн Подстановка [Флаги].
- Паттерн — регулярное выражение (PCRE). Оно матчится против пути запроса. В
.htaccessв корне сайта путь подаётся без ведущего/: запрос/blog/5→ паттерн видитblog/5. (В.htaccessподкаталога — относительно этого каталога; в конфиге Apache на уровне сервера и в<VirtualHost>— с ведущим/.) Ведущий!инвертирует паттерн. - Подстановка — во что превратить путь:
-— не менять (полезно, когда нужно только применить флаг, напр.[F]); путь, начинающийся с/, или полный URLhttps://…/http://…///host/…— см. ниже про внешний редирект; иначе — относительный путь (в.htaccessподкаталога — относительноRewriteBase). В подстановке доступны бэкрефы$1,$2… (захваты групп паттерна;$0— весь матч),%1,%2… (захваты последнегоRewriteCond),%{ИМЯ}(серверная переменная). - Флаги — в квадратных скобках через запятую:
[R=301,L]. См. следующий раздел.
Пример — ЧПУ для блога (/blog/5 обрабатывается так, будто пришёл запрос /post.php?id=5):
RewriteEngine On RewriteRule ^blog/(\d+)$ post.php?id=$1 [L]
^blog/(\d+)$ — «строка начинается с blog/, дальше одна или больше цифр (захват в группу 1), и больше ничего»; $1 в подстановке = эти цифры.
Внутренняя перезапись vs внешний редирект — ключевое различие. Внутренняя перезапись: путь меняется только внутри сервера, браузер ничего не видит, адрес в строке не меняется — так работает RewriteRule . index.php [L] (фронт-контроллер) или пример выше. Внешний редирект: сервер отвечает кодом 30x и Location:, браузер переходит по новому адресу — так работает RewriteRule с флагом [R] (или [R=301] и т. п.), либо если подстановка — полный URL со схемой (https://…). Пример внешнего редиректа:
RewriteEngine On RewriteRule ^old-page$ /new-page [R=301,L]
Флаги RewriteRule
L— «last»: остановить обработку правил в этом проходе. Но в.htaccess(per-directory контекст) Apache 2.4 после любой перезаписи перезапускает весь набор правил с начала — и так, пока URL не перестанет меняться. Поэтому[L]≠ «больше ничего не делать никогда». Для окончательного «стоп» —END.END— окончательно остановить обработку и не перезапускать набор (Apache 2.4+).R/R=код— внешний редирект. Без значения —302(временный). Для постоянного редиректа —R=301. МожноR=302,R=303,R=307; для блокировок —F(даёт403) иG(410) обычно понятнее.NC— «nocase»: матчить паттерн (и вRewriteCond—CondPattern) без учёта регистра.QSA— «query string append»: присоединить исходную строку запроса (?…) к новой. БезQSAquery из подстановки заменяет исходный; если в подстановке?нет — исходный query сохраняется как есть.QSD— «query string discard»: отбросить строку запроса (Apache 2.4+).F— «forbidden»: вернуть403 Forbidden(подстановку обычно пишут-).G— «gone»: вернуть410 Gone.C/chain— «chain»: следующее правило выполнится, только если это сработало. Если это правило не сматчилось — следующее (и дальше по цепочке) пропускается.S=N/skip=N— при срабатывании правила пропустить следующиеNправил (что-то вродеgoto).NE— «noescape»: не URL-кодировать спецсимволы в подстановке (?,#,%и т. п. останутся как есть).E=ИМЯ:ЗНАЧЕНИЕ— выставить переменную окружения (доступна потом как%{ENV:ИМЯ}и в скриптах как$_SERVER['ИМЯ']).N/next— перезапустить обработку набора правил с начала немедленно (а не после прохода). Легко получить бесконечный цикл — используйте осторожно.P/proxy— проксировать запрос черезmod_proxy(вместо редиректа).NS(nosubreq),T=тип(type),B,DPI(discardpath),PT(passthrough),H=обработчик(handler) — реже; см. документацию Apache.
RewriteCond: условия перед правилом
Синтаксис: RewriteCond ТестоваяСтрока ШаблонУсловия [Флаги]. RewriteCond ставится перед RewriteRule и относится к ближайшему следующему RewriteRule. Несколько RewriteCond подряд — все должны быть истинны (логическое И). Флаг [OR] на условии объединяет его со следующим в группу-«ИЛИ»: RewriteCond A [OR] + RewriteCond B → правило сработает, если A или B; группы между собой по-прежнему через И.
- ТестоваяСтрока — что проверяем; обычно серверная переменная (
%{HTTP_HOST}), можно с бэкрефами%N/$Nи текстом. - ШаблонУсловия — это: регулярное выражение (PCRE) — истинно, если ТестоваяСтрока ему соответствует; лексикографические сравнения
<строка(меньше),>строка(больше),=строка(точно равна),<=строка,>=строка(Apache 2.4); числовые сравнения (Apache 2.4)-eq,-ne,-lt,-le,-gt,-ge(обе стороны как целые); проверки файловой системы-f(существующий обычный файл),-d(каталог),-l(символьная ссылка),-s(непустой файл),-x(исполняемый) — обычно ТестоваяСтрока тут%{REQUEST_FILENAME}; ведущий!перед любым из перечисленного — инверсия; флаги[NC](без учёта регистра),[OR].
Классический пример — «фронт-контроллер»: всё, что не существующий файл и не существующий каталог, отдать в index.php:
RewriteEngine On RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteRule . index.php [L]
(%{REQUEST_FILENAME} — полный путь к файлу на диске, который соответствовал бы запросу; !-f — «это не файл», !-d — «это не каталог»; паттерн . матчит любой непустой путь.)
Пример с [OR] — редирект и example.com, и www.example.com на https://example.com:
RewriteEngine On RewriteCond %{HTTP_HOST} ^(www\.)?example\.com$ [NC] RewriteCond %{HTTPS} off [OR] RewriteCond %{HTTP_HOST} ^www\. [NC] RewriteRule ^ https://example.com%{REQUEST_URI} [R=301,L]
(Первое условие ограничивает домен; затем «не-HTTPS или с www» → редиректим на канонический https://example.com.)
Переменные %{…}
%{HTTP_HOST}/%{SERVER_NAME}— хост из запроса (example.com).%{HTTPS}—on, если запрос по HTTPS, иначеoff. (За reverse-proxy/CDN может всегда бытьoff— тогда смотрите%{HTTP:X-Forwarded-Proto}.)%{REQUEST_SCHEME}—httpилиhttps.%{SERVER_PORT}— порт (80,443, …).%{REQUEST_URI}— путь запроса (/blog/5), без строки запроса. Меняется при внутренней перезаписи.%{QUERY_STRING}— то, что после?(без?).%{REQUEST_METHOD}—GET,POST,HEAD, …%{REQUEST_FILENAME}/%{SCRIPT_FILENAME}— полный путь к файлу на диске, соответствующий запросу (=DOCUMENT_ROOT+ путь); используется в проверках-f/-d.%{DOCUMENT_ROOT}— корневой каталог сайта на диске.%{THE_REQUEST}— полная первая строка HTTP-запроса (GET /blog/5?x=1 HTTP/1.1). Не меняется при внутренних перезаписях — поэтому используется в правилах-редиректах, чтобы не зациклиться (см. «частые ошибки»).%{HTTP_USER_AGENT}— строка User-Agent клиента (для блокировки ботов).%{HTTP_REFERER}— заголовок Referer (для защиты от хотлинка / блокировки трафика).%{HTTP_COOKIE}— заголовок Cookie.%{HTTP:Имя-Заголовка}— любой входящий HTTP-заголовок, напр.%{HTTP:X-Forwarded-Proto},%{HTTP:Accept-Language}.%{ENV:ИМЯ}— переменная окружения (например, выставленная флагом[E=…]или директивойSetEnvIf).%{REMOTE_ADDR}— IP-адрес клиента.%{TIME},%{TIME_YEAR},%{TIME_MON},%{TIME_DAY},%{TIME_HOUR},%{TIME_MIN},%{TIME_SEC},%{TIME_WDAY}— текущее время сервера (TIME—ГГГГММДДЧЧММСС).%{IS_SUBREQ}—true, если это внутренний под-запрос (например, приInclude/негоциации), иначеfalse.
Запомните разницу: %{REQUEST_URI} меняется при внутренней перезаписи, %{THE_REQUEST} — нет. Поэтому в правиле-редиректе, который должен сработать только на «оригинальный» URL, проверяют %{THE_REQUEST}.
Бэкрефы: $N (правило) и %N (условие)
$1…$9 — то, что захватили группы ( … ) в паттерне последнего сматчившего RewriteRule; $0 — весь матч. Используются в подстановке этого RewriteRule.
%1…%9 — захваты последнего сматчившего RewriteCond-паттерна; %0 — весь матч. Используются и в ТестовойСтроке последующих RewriteCond той же цепочки, и в подстановке RewriteRule.
Пример с обоими — редирект с www на без www, сохраняя путь:
RewriteEngine On RewriteCond %{HTTP_HOST} ^www\.(.+)$ [NC] RewriteRule ^(.*)$ https://%1/$1 [R=301,L]
Здесь %1 = всё после www. (домен без www.), $1 = путь запроса. (Если у сайта нет TLS — поставьте http:// вместо https://.)
RewriteBase
RewriteBase нужна для .htaccess в подкаталоге и только для относительных подстановок: она говорит mod_rewrite, какой URL-префикс соответствует этому каталогу, чтобы правильно построить итоговый URL.
Пример — .htaccess лежит в /app/, доступном как https://example.com/app/:
RewriteEngine On RewriteBase /app/ RewriteRule ^login$ login.php [L]
Запрос /app/login обрабатывается как /app/login.php. Без RewriteBase в подкаталоге относительная подстановка login.php может «уехать» (mod_rewrite попытается угадать базу по физическому пути).
Когда RewriteBase не нужна: если это .htaccess в корне сайта; или если все ваши подстановки абсолютные (начинаются с / — относительно DocumentRoot) либо это полные URL. Многие современные конфиги пишут подстановки абсолютными именно чтобы не зависеть от RewriteBase.
Порядок обработки и [L]/[END] в .htaccess
Правила в .htaccess обрабатываются сверху вниз. Каждое подходящее RewriteRule меняет текущий URL; затем, если не было [L], обработка продолжается со следующего правила — уже с новым URL. Поэтому порядок правил важен, и без [L] правила могут «накладываться».
Тонкость .htaccess-контекста (per-directory): в Apache 2.4 после того как набор правил отработал и URL изменился, mod_rewrite запускает весь набор заново — и так до тех пор, пока проход не перестанет менять URL (или не наберётся ~10 итераций — тогда 500). Поэтому [L] останавливает только текущий проход, а не «всю обработку навсегда». Если нужно действительно остановиться и не перезапускать набор — флаг [END] (Apache 2.4+). Непонимание этого — частый источник лупов и неожиданного поведения.
Ещё раз про результат правила: внутренняя перезапись (RewriteRule . index.php [L]) — браузер ничего не видит, адрес не меняется; внешний редирект ([R], [R=301] или подстановка с полным URL) — браузер получает 30x и меняет адрес. Не путайте: если хотели «красивый URL без редиректа», флага [R] быть не должно.
Частые ошибки и грабли
- Нет
RewriteEngine On— правила молча игнорируются. Проверьте, что эта строка есть и стоит до правил (в каждом.htaccess, где есть свои правила). 500 Internal Server Error, в логе «Invalid command 'RewriteRule'» —mod_rewriteне включён на сервере/хостинге. Включите модуль (или попросите хостинг), либо оберните правила в<IfModule mod_rewrite.c> … </IfModule>, чтобы хотя бы не падало.- Относительная подстановка с
[R]—RewriteRule ^old$ new-page [R=301]приведёт к редиректу на адрес, достроенный черезDocumentRoot(часто не туда). Указывайте абсолютный путь/new-pageили полный URLhttps://example.com/new-page. (Проверить, во что превратится URL, можно в тестере /rewrite/.) - Бесконечный редирект —
ERR_TOO_MANY_REDIRECTS— обычно: правило-редирект проверяет%{REQUEST_URI}(который меняется при перезаписи) вместо%{THE_REQUEST}(который нет); либо редирект ведёт на путь, который сам же снова попадает под правило. Используйте%{THE_REQUEST}для проверки «оригинального» URL и убедитесь, что цель редиректа под правило больше не подпадает. - Забыли
[L]— правила «текут» дальше: следующее правило применяется к уже переписанному URL и накладывает ещё одну трансформацию. Ставьте[L]там, где обработка должна остановиться. - Не экранировали
.в паттерне — точка в регулярке означает «любой символ»:example.comматчит иexampleXcom, иexample-com. Если нужна буквальная точка —example\.com. То же для?,+,(,),[,],*,^,$и т. п. — в паттерне это спецсимволы. .htaccessв подкаталоге не наследует правила родителя — по умолчаниюRewriteRuleиз.htaccessверхнего уровня не применяются в.htaccessподкаталога; нужноRewriteOptions InheritDownв родительском.htaccess(илиRewriteOptions InheritDownBefore), либоRewriteOptions Inheritв дочернем.- Путать перезапись и редирект —
RewriteRule . index.php [L]НЕ меняет адрес в браузере (это внутренняя перезапись), аRewriteRule ^x$ /y [R=301,L]— меняет. Если адрес «не меняется, а должен» — добавьте[R]; если «меняется, а не должен» — уберите[R]. - 404 после включения ЧПУ, бесконечный редирект, правило не срабатывает — типовые причины и их исправление разобраны в гиде «Почему .htaccess не работает».
- Сомневаетесь в самой регулярке (что значит
[^/],\.,(.*), как нумеруются$1/$2) — разбор синтаксиса PCRE в справочнике «PCRE: регулярные выражения для mod_rewrite».
Проверить и собрать
Тестер RewriteRule — прогнать свои правила на конкретном URL (пошаговая трассировка: какое правило сработало, какие RewriteCond прошли, во что превратился адрес, итог).
Тестер регулярных выражений — проверить отдельный regex-паттерн на примерах строк, увидеть захваты $1/$2 и разбор паттерна.
Объяснялка .htaccess — построчный разбор файла, включая флаги RewriteRule и распознавание типовых блоков.
Линтер проверка .htaccess — синтаксис файла, баланс блоков, устаревшие директивы.
Генератор .htaccess — блоки на mod_rewrite (редиректы, HTTPS, блокировка ботов/хотлинка, режим обслуживания) собираются чекбоксами.
Пакетный генератор 301-редиректов — много 301 из списка «старый URL → новый URL».
Рецепты .htaccess — готовые блоки под типовые задачи.
Справочник директив .htaccess — что делает каждая директива, синтаксис, пример.
Статьи: блокировка по referer, фильтрация по IP.
«.htaccess и nginx» — таблица соответствий и почему nginx не читает .htaccess.