mod_rewrite — самый мощный и самый опасный модуль Apache: одна неверная строка способна отправить сайт в бесконечный редирект. При этом большинство задач решается несколькими понятными правилами: переход на HTTPS, склейка www, человекочитаемые URL и массовые переадресации после смены структуры сайта. Ниже — рабочие конфигурации для Apache 2.4, порядок применения и способы отладки, когда правило не срабатывает.
Включение и точка применения
Сначала проверьте, загружен ли модуль, и разрешите переопределения. В Debian, Ubuntu и Linux Mint модуль включается отдельно, в Fedora и RHEL он обычно уже собран в пакете httpd.
sudo a2enmod rewrite
sudo apachectl -M | grep rewrite
sudo apachectl configtest
sudo systemctl reload apache2
Правила в .htaccess работают только при AllowOverride FileInfo или All. Если в конфигурации стоит AllowOverride None, файл будет молча игнорироваться — это первая причина «редирект не работает».
<Directory /var/www/example.ru>
Options FollowSymLinks
AllowOverride FileInfo
Require all granted
</Directory>
Правила можно держать в виртуальном хосте, в <Directory> или в .htaccess. Разница принципиальна: в .htaccess путь в шаблоне не начинается со слеша, а сам файл читается при каждом запросе. Для нагруженного сайта правила лучше перенести в конфигурацию виртуального хоста.
Первый редирект: HTTP на HTTPS
Классическая задача — перевести весь сайт на защищённый протокол, сохранив путь и query-строку.
<VirtualHost *:80>
ServerName example.ru
ServerAlias www.example.ru
RewriteEngine On
RewriteCond %{HTTPS} !=on
RewriteRule ^ https://example.ru%{REQUEST_URI} [R=301,L]
</VirtualHost>
Флаг R=301 обозначает постоянный редирект, L останавливает обработку текущего набора правил. %{REQUEST_URI} уже содержит путь и query-строку, поэтому ничего не теряется. Если Apache стоит за обратным прокси и сам принимает HTTP, проверка %{HTTPS} всегда даст off; тогда смотрят на заголовок прокси.
RewriteCond %{HTTP:X-Forwarded-Proto} !https
RewriteRule ^ https://example.ru%{REQUEST_URI} [R=301,L]
Заголовок X-Forwarded-Proto должен приходить только от доверенного прокси, иначе клиент подделает его, и проверка потеряет смысл. HSTS добавляйте после того, как убедились, что HTTPS работает на всех поддоменах: Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains" (нужен mod_headers).
Канонический хост и завершающий слеш
Дубли страниц по адресу с www и без него лучше закрывать на уровне сервера, а не в CMS.
RewriteEngine On
RewriteCond %{HTTP_HOST} ^www\.example\.ru$ [NC]
RewriteRule ^ https://example.ru%{REQUEST_URI} [R=301,L]
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.+)/$ /$1 [R=301,L]
Первый блок склеивает www-версию с основным доменом, второй убирает слеш у файлов и несуществующих каталогов. Проверка !-d не даёт сломать реальные каталоги, которым слеш нужен. Флаг NC делает сравнение регистронезависимым, а NE пригодится, если в URL есть символы вроде #, которые нельзя повторно кодировать.
ЧПУ и фронт-контроллер
Схема, знакомая по WordPress, Laravel и десяткам самописных CMS: если запрошенного файла или каталога нет, запрос уходит в index.php.
RewriteEngine On
RewriteBase /
RewriteCond %{REQUEST_FILENAME} -f [OR]
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule ^ - [L]
RewriteRule ^ index.php [L]
Первое правило с прочерком означает «ничего не менять, но прекратить обработку»: существующие файлы отдаются как есть. Второе передаёт всё остальное фронт-контроллеру. В .htaccess шаблон не содержит ведущего слеша, в конфигурации сервера — содержит. Если после включения правил перестала открываться админка, проверьте, не попадает ли её путь под общее правило, и добавьте исключение через RewriteCond %{REQUEST_URI} !^/admin/.
Для простого фронт-контроллера в Apache 2.4 есть более короткая запись FallbackResource /index.php. Она не заменяет mod_rewrite целиком, но убирает необходимость в трёх строках и работает предсказуемее.
Массовые редиректы через RewriteMap
После переезда сайта на новую структуру накапливаются сотни старых адресов. Писать по правилу на каждый — плохая идея: конфигурация разрастается, а скорость падает. На помощь приходит карта соответствий.
RewriteMap legacy txt:/etc/apache2/legacy-map.txt
RewriteCond ${legacy:%{REQUEST_URI}|} !^$
RewriteRule ^ https://example.ru${legacy:%{REQUEST_URI}} [R=301,L]
Файл карты содержит пары «старый путь новый путь», по одной в строке. RewriteMap допустим только в контексте сервера или виртуального хоста, в .htaccess его не будет. После правки карты выполните graceful restart, чтобы исключить рассинхрон между файлом и запущенными процессами. Для больших таблиц вместо txt используют dbm, но для нескольких тысяч строк текстового файла достаточно.
Отладка правил
mod_rewrite не пишет отдельный лог с версии 2.4: вместо устаревшей директивы RewriteLog включают трассировку через LogLevel.
LogLevel alert rewrite:trace3
sudo apachectl configtest
sudo apachectl -S
sudo tail -f /var/log/apache2/error.log
curl -sI http://example.ru/old-page
curl -s -o /dev/null -w "%{http_code} %{redirect_url}\n" http://example.ru/old-page
Уровень trace3 показывает, какое правило сработало и какие условия проверялись; trace5 избыточен. apachectl -S выводит виртуальные хосты и помогает понять, какой конфиг вообще обслуживает домен. curl -I показывает код и заголовок Location, а второй вызов удобно вставлять в проверки после выкладки.
Типичные ошибки
- Цикл редиректа: правило перенаправляет на адрес, который снова попадает под это же условие.
- Отсутствие
L: после срабатывания правила Apache продолжает проверять остальные. - Ведущий слеш в шаблоне
.htaccess— правило не совпадёт никогда. - Потеря query-строки: при замене URL на статический адрес нужен флаг
QSA. AllowOverride Noneили отсутствиеFollowSymLinks— файл правил игнорируется.- Кодирование URL:
%{REQUEST_URI}уже закодирован, повторное кодирование ломает кириллические адреса.
Безопасность и производительность
Переадресация — не только SEO. Несколькими правилами можно закрыть служебные файлы и вернуть 410 для удалённых разделов, не отдавая посетителям мягкую ошибку 404.
RewriteRule (^|/)\.(?!well-known) - [F,L]
RewriteRule ^old-section/ - [G,L]
RewriteCond %{REQUEST_METHOD} !^(GET|HEAD)$
RewriteRule ^ - [F,L]
Первый шаблон запрещает доступ к скрытым файлам, кроме .well-known, где живут проверки сертификатов. Второй возвращает 410 Gone для снятого раздела. Третий пример ограничивает методы, но применять его ко всему сайту рискованно: формы и API используют POST. Ставьте такие правила точечно и всегда проверяйте configtest перед reload.
По возможности заменяйте простые случаи директивами Redirect и RedirectMatch — они дешевле. Не держите сотни правил в .htaccess: каждое читается на каждом запросе. 301 кэшируется браузером надолго: тестируйте новые правила с кодом 302, а постоянный включайте после проверки.
Итог
mod_rewrite решает четыре повседневные задачи: протокол, канонический хост, маршрутизацию на фронт-контроллер и массовые переадресации. Главное — держать правила в конфигурации сервера, включать L там, где нужно остановиться, и проверять результат через curl и трассировку. Начните с двух-трёх правил, убедитесь, что они не создают циклов, и только потом добавляйте карты и исключения.