Создание правил в .htaccess
Сначала проверьте сервер
Одна и та же строка .htaccess на одном хостинге работает, а на другом отвечает 500 на всех страницах. Прежде чем писать правило, выясните, какой сервер читает файл, как запущен PHP и где заканчивается TLS. На это отвечает несколько коротких команд. Инструменты здесь: curl, SSH, grep.
Заголовки ответа называют сервер, но не режим PHP:
curl -sSI https://example.com/
HTTP/2 200 content-type: text/html; charset=UTF-8 server: Apache
Файл-зонд в корне сайта печатает режим PHP и переменные TLS. Дайте ему случайное имя и удалите после проверки:
vi ~/example.com/probe-7f3a.php
<?php
echo "HTTPS=", var_export($_SERVER['HTTPS'] ?? null, true),
" XFP=", var_export($_SERVER['HTTP_X_FORWARDED_PROTO'] ?? null, true),
" SERVER_SOFTWARE=", $_SERVER['SERVER_SOFTWARE'] ?? '',
" sapi=", PHP_SAPI, "\n";
curl -sS https://example.com/probe-7f3a.php
На FastCGI-хостинге (DreamHost) зонд отвечает:
HTTPS='on' XFP=NULL SERVER_SOFTWARE=Apache sapi=cgi-fcgi
На своём сервере посмотрите модули Apache. apache2ctl -M нужен root или sudo и сервер, которым вы управляете; общий хостинг такую команду обычно не даёт. Это локальный Docker php:8.5-apache с mod_php:
apache2ctl -M | grep -i -E 'php|rewrite|alias|headers|version'
version_module (static) alias_module (shared) headers_module (shared) php_module (shared) rewrite_module (shared)
На FastCGI-хостинге (DreamHost) PHP работает отдельным процессом:
pgrep -l -u user php
2745746 php85.cgi
Бинарник Apache тоже там:
ls /usr/sbin/apache2*
/usr/sbin/apache2 /usr/sbin/apache2ctl
Когда правило всё-таки сломало сайт, журнал ошибок называет файл и директиву:
tail -n 1 ~/logs/example.com/https/error.log
[Fri Nov 06 23:29:54 2026] [example.com] [alert] [client 203.0.113.7:37118] [pid 3064457] config.c(2045): /home/user/example.com/.htaccess: Invalid command 'php_flag', perhaps misspelled or defined by a module not included in the server configuration
sapi=cgi-fcgi вместе с php85.cgi значит, что PHP работает отдельным процессом FastCGI, а не как mod_php: php_flag там неизвестен и останавливает Apache с ответом 500. sapi=apache2handler значит mod_php, где те же строки работают. Подробный разбор, значение каждого ответа и безопасная проверка того, что .htaccess вообще читается: Какой у вас сервер.
Какие правила где безопасны
Читайте строку так: слева группа директив, дальше что происходит на каждом сервере. Безопасно - правило работает как написано. Защитить - обернуть в IfModule или If, чтобы сервер без модуля пропустил блок, а не упал. Ломает сайт - одна неизвестная директива отвечает 500 для папки и всех папок ниже. Игнорируется - сервер вообще не читает .htaccess.
| Группа директив | Apache + mod_php | Apache + FastCGI | Apache за TLS-прокси | Только nginx | LiteSpeed |
|---|---|---|---|---|---|
| Redirect / RedirectMatch | безопасно | безопасно | только абсолютный адрес https://: цель из одного пути добавляет шаг на http | игнорируется | безопасно |
| RewriteRule | безопасно | безопасно | безопасно: схему задаёт переменная окружения | игнорируется | безопасно |
| php_flag / php_value | безопасно | ломает сайт | безопасно: зависит от режима PHP | игнорируется | сверьтесь с документацией хостинга |
| Header | защитить: <IfModule mod_headers.c> | защитить: <IfModule mod_headers.c> | защитить: <IfModule mod_headers.c> | игнорируется: add_header в конфигурации сервера | безопасно |
| Options | безопасно | безопасно | безопасно | игнорируется | защитить: сверьтесь с документацией хостинга |
| AddHandler / SetHandler для PHP | защитить | защитить: не направляйте PHP в mod_php | защитить | игнорируется | защитить |
Директива, которую AllowOverride не разрешает в этой папке (Options, например), тоже отвечает 500.
Строки про FastCGI и PHP-FPM следуют руководству PHP о файлах .user.ini: php_value в .htaccess относится к режиму модуля Apache, а CGI и FastCGI читают .user.ini. Страница LiteSpeed о правилах rewrite говорит, что LiteSpeed читает правила rewrite в стиле Apache. Страница настроек PHP пользователя рассказывает про PHPRC и PHP_INI_SCAN_DIR и говорит, что .user.ini не рекомендуется в LSWS; она не говорит, что LiteSpeed берёт php_value из .htaccess, поэтому настройки PHP сверяйте с документацией хостинга. Защитные блоки и план отката: Безопасные правила, защитные блоки и откат.
Redirect: простые переносы
Одна страница переехала на новый адрес. mod_alias делает это одной строкой - сначала источник, потом цель:
Redirect 301 /old/page.php /new/page.php
301 ставьте для постоянного переноса (вес ссылок передаётся, браузер запоминает ответ), 302 - пока экспериментируете. Ошибочный 301 браузер помнит долго после исправления файла, поэтому проверяйте на 302 и переключайте на 301, когда уверены.
На хостинге, где HTTPS заканчивается на прокси спереди, цель из одного пути сначала отправляет посетителя на http://. Безопасные варианты: Redirect 301 или пара и Редиректы за HTTPS-прокси.
RedirectMatch: шаблоны
Обычный Redirect совпадает с префиксом адреса и приклеивает остаток к цели. Чтобы закрыть целое дерево одной строкой, задайте регулярное выражение с якорем:
RedirectMatch 301 ^/edu/suomen/.*$ /edu/index.php
Без регулярного выражения /edu/suomen/x.php попал бы на /edu/index.phpx.php. Точное совпадение одного адреса работает так же:
RedirectMatch 301 ^/robot/?$ /qa/robot/
Якорь /?$ значит, что подпути вроде /robot/basics/ не трогаются - перенаправляется только сам каталог.
Основы RewriteRule
Когда цель зависит от части источника, mod_rewrite захватывает её скобками и подставляет как $1, $2:
RewriteEngine On RewriteRule ^blog/([0-9]{4})/([a-z-]+)\.php$ /archive/$1/$2/ [R=301,L]
RewriteRule видит путь без ведущего слэша и без строки запроса. Чтобы сохранить параметры запроса, добавьте флаг QSA. Условие над правилом видит полный %{REQUEST_URI}, включая ведущий слэш, а захваченные группы возвращает как %1.
Важные флаги
- R=301 / R=302 - внешний редирект и его статус. Без R перезапись внутренняя: адрес в браузере не меняется.
- L (last) - остановить обработку следующих правил после совпадения. Он нужен почти каждому правилу редиректа.
- NC (nocase) - совпадение без учёта регистра, удобно для старых адресов со смешанным регистром.
- QSA (qsappend) - добавить исходную строку запроса к цели, а не отбросить её.
- NE (noescape) - не кодировать цель процентными последовательностями, нужно для якорей #.
Порядок правил
Apache проверяет правила сверху вниз, и жадное правило-префикс перекрывает всё ниже. Точные правила идут первыми, общие - последними:
Redirect 301 /pc/ /latex/ Redirect 301 /pc/LaTeX.php /latex/man/
Apache берёт первую совпавшую строку, и совпадение по префиксу перекрывает более точную строку ниже. /pc/LaTeX.php совпадает с префиксом /pc/, поэтому посетитель попадает на /latex/LaTeX.php, а не на /latex/man/: точную строку ставьте первой.
Оставьте комментарий о порядке в файле, иначе следующий редактор вернёт его обратно. Правила mod_rewrite выполняются раньше правил mod_alias независимо от порядка строк; не смешивайте обе системы в одном файле (почему).
Проверка правил
Не проверяйте сначала в браузере - кэш 301 обманывает. Спросите заголовки:
curl -s -o /dev/null -w "%{http_code} %{redirect_url}\n" https://example.com/robot/
301 https://example.com/qa/robot/
Проверяйте цепочку, а не только первый шаг: два редиректа подряд удваивают задержку и теряют вес в поиске. Цель - один шаг от старого адреса до конечной страницы.
Частые ошибки
- Склейка префикса: Redirect 301 /edu/suomen/ на файл дописывает остаток. Для закрытия дерева используйте RedirectMatch с .*$.
- Цели редиректов должны существовать. Сверяйте их с корнем сайта: редирект на отсутствующую страницу меняет 404 на более медленный 404.
- Redirect срабатывает, даже если по адресу источника лежит настоящий файл, поэтому устаревшее правило может перехватить живую страницу. Удаляйте правила, чей источник снова ожил.
- Завершающие слэши: ^/robot/?$ покрывает оба варианта; без ? один из них отдаёт 404.
- Цепочки редиректов (/linux/virtualbox.php на /virtualbox/ на /dvps/virtualbox/) работают, но стоят по дополнительному запросу - при правке сводите их к одному шагу.
- За прокси, который заканчивает TLS, Redirect и RedirectMatch отвечают Location с http://. Смотрите Редиректы за HTTPS-прокси.
- Неизвестная директива, например php_flag на хостинге с FastCGI, отвечает 500 на всех страницах ниже файла. Защитите её или вынесите настройку из .htaccess: Безопасные правила.
- Переезд меняет сервер под тем же файлом. Прочитайте Перенос сайта на другой хостинг до переключения и сравните старый и новый хостинг в статьях Общий хостинг Beget, Общий хостинг DreamHost и Docker-образы хостингов. Какой хостинг выбрать — обзор.
Дисциплина та же, что в коде: одно правило на один перенос, комментарий над неочевидным правилом и периодическая проверка всех целей.
Старая глава про HTTPS-прокси теперь отдельная статья: Редиректы за HTTPS-прокси.
Статьи по теме
- Какой у вас сервер - заголовки ответа, PHP-зонд, модули Apache, процесс PHP, журнал ошибок и безопасная проверка того, что .htaccess читается.
- Безопасные правила, защитные блоки и откат - резервная копия, проверка в подпапке, блоки IfModule и If, и куда девать настройки PHP, когда php_flag нельзя.
- Redirect 301 или пара - когда хватает одной строки Redirect и когда нужна пара RewriteCond плюс RewriteRule.
- Редиректы за HTTPS-прокси - почему Apache за прокси пишет http:// и как этот сайт перевёл все редиректы на пары mod_rewrite.
Автор статьи: Андрей Олегович