Коротко. .htaccess — это файл конфигурации Apache, который действует на каталог, где он лежит, и на все вложенные каталоги. Apache перечитывает его на каждом запросе, поэтому правки применяются сразу, без перезапуска. Он работает только если администратор разрешил переопределение директивой AllowOverride, и полностью игнорируется nginx.
Что такое .htaccess и когда он вообще работает
Apache читает основной конфиг один раз при старте. Кроме него он умеет читать «распределённые» файлы конфигурации — по одному на каталог. Имя такого файла по умолчанию .htaccess. Смысл механизма в том, чтобы владелец сайта на общем хостинге мог менять поведение веб-сервера в своём каталоге, не имея доступа к главному конфигу и не имея права перезапускать сервис.
Три условия, без которых файл не сработает вообще:
- Сайт обслуживает Apache (или совместимый с ним LiteSpeed). Чистый nginx не читает .htaccess ни при каких настройках — это принципиальное архитектурное отличие, а не недоработка.
- Разрешено переопределение. В конфиге виртуального хоста для вашего каталога должно стоять
AllowOverrideсо значением, отличным отNone. Если стоитNone, Apache даже не откроет файл — он будет лежать и молча ничего не делать. - Подключён нужный модуль. Директива
RewriteRuleбезmod_rewrite,ExpiresByTypeбезmod_expiresиHeaderбезmod_headersдают не «правило не сработало», а ошибку 500 на весь каталог.
Значение AllowOverride задаёт не «да/нет», а список групп директив: FileInfo (редиректы и rewrite), AuthConfig (парольная защита), Limit (доступ по IP), Indexes (листинг каталога), Options. Отсюда типичная ситуация на хостинге: редиректы работают, а Options -Indexes выдаёт 500 — разрешена одна группа, но не другая.
Где лежит .htaccess и как его создать
Канонического пути нет: файл лежит в корне сайта, а корень у каждого хостинга называется по-своему — public_html, www, htdocs, httpdocs, site/public. Ориентируйтесь не на имя каталога, а на то, где лежит index.php или index.html, отдаваемый по адресу сайта.
Имя начинается с точки, поэтому файл скрытый. В файловом менеджере панели и в FTP-клиенте включите показ скрытых файлов, иначе будете уверены, что файла нет, и создадите второй.
# создать пустой файл и посмотреть, что уже есть в корне
cd /var/www/example.com/public_html
ls -la | grep -i htaccess
printf '' > .htaccess
chmod 644 .htaccess
Требования к самому файлу простые, но именно на них ломаются правки:
- Кодировка — UTF-8 без BOM. Три невидимых байта BOM в начале файла Apache считает мусором до первой директивы и отвечает 500.
- Переводы строк — LF. Файл, сохранённый Блокнотом с CRLF, обычно работает, но в директивах с продолжением строки и в кавычках даёт непредсказуемые эффекты.
- Права
644, владелец — пользователь, от которого работает сайт. Файл с правами600и чужим владельцем веб-сервер прочитать не сможет. - Каждая директива — на своей строке, комментарии начинаются с
#. Комментарий в конце строки с директивой недопустим: он будет разобран как аргумент.
Перед любой правкой сделайте копию. cp .htaccess .htaccess.bak-$(date +%F) — это одна секунда, а откатывать сломанный редирект по памяти на проде вы будете долго. Общий подход к резервным копиям — в материале про бэкап сайта.
Как Apache применяет директивы: каскад и наследование
Получив запрос, Apache определяет путь к файлу на диске и проходит по всем каталогам от корня документов до целевого, читая .htaccess в каждом. Директивы объединяются: файл в подкаталоге дополняет родительский, а при конфликте — переопределяет его.
Из этого следуют два неочевидных практических вывода.
Первый: правила mod_rewrite по умолчанию не наследуются. Если в корне описан фронт-контроллер, а в подкаталоге вы создали свой .htaccess с любым RewriteRule, набор правил родителя для этого подкаталога перестаёт применяться целиком. Вернуть наследование можно директивой RewriteOptions Inherit, но чаще правильнее не плодить файлы, а держать все правила в корневом.
Второй: RewriteBase задаёт базовый путь для правил с относительной заменой. При переносе сайта из подкаталога в корень (или наоборот) забытый RewriteBase — самая частая причина того, что «на старом хостинге всё работало».
Внутри одного файла директивы применяются сверху вниз. Флаг [L] прекращает обработку текущего прохода, но если правило сделало внутреннюю подмену пути, Apache запускает набор правил заново — отсюда циклы, о которых ниже.

Редиректы: HTTPS, склейка www и отдельные страницы
Для простых случаев «путь → путь» хватает Redirect из mod_alias — он быстрее и читается однозначно. RewriteRule нужен там, где решение зависит от условий: схемы, хоста, наличия файла, заголовка.
# Простой постраничный переезд (mod_alias)
Redirect 301 /old-page.html /new-page/
RedirectMatch 301 ^/blog/([0-9]+)/(.*)$ /articles/$2
# Канонический адрес одним хопом (mod_rewrite)
RewriteEngine On
# 1) www → без www, сразу на https
RewriteCond %{HTTP_HOST} ^www\.(.+)$ [NC]
RewriteRule ^ https://%1%{REQUEST_URI} [R=301,L]
# 2) http → https; за обратным прокси схему сообщает заголовок, а не %{HTTPS}
RewriteCond %{HTTPS} !=on
RewriteCond %{HTTP:X-Forwarded-Proto} !=https
RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]
Порядок правил здесь не случаен. Запрос на http://www.example.com/page попадает в первое правило и уходит на https://example.com/page за один переход. Если поменять правила местами, тот же запрос сделает два перехода: сначала на https с www, потом на https без www. Лишний хоп — это лишний RTT для пользователя и размытие сигнала для поисковых систем; разбор в материалах про 301 и 302 и редиректы и SEO.
Отдельно про %{HTTPS}. Если перед Apache стоит nginx, балансировщик или CDN, TLS терминируется на них, и до Apache запрос доходит по http. Переменная %{HTTPS} в этом случае всегда равна off, а условие «если не https — редиректить» выполняется на каждом запросе. Результат — бесконечный цикл и ERR_TOO_MANY_REDIRECTS у всех посетителей. Проверять нужно заголовок, который выставляет прокси.

Доступ: запрет по IP, пароль на каталог, защита служебных файлов
Синтаксис контроля доступа поменялся между Apache 2.2 и 2.4, и это источник большинства «внезапных» пятисоток при переезде. Старые директивы Order, Allow from, Deny from в 2.4 работают только при подключённом модуле совместимости mod_access_compat; если его нет — ошибка конфигурации.
# Apache 2.4 — актуальный синтаксис
<RequireAll>
Require all granted
Require not ip 203.0.113.10
Require not ip 198.51.100.0/24
</RequireAll>
# Apache 2.2 — устаревший синтаксис (нужен mod_access_compat)
Order allow,deny
Allow from all
Deny from 203.0.113.10
Пароль на каталог состоит из двух частей: файла с хешами паролей и директив, которые на него ссылаются. Файл с хешами нельзя держать внутри публичного каталога — иначе его можно скачать.
# создать файл с паролем ВНЕ корня сайта
htpasswd -c /var/www/example.com/.htpasswd admin
# .htaccess в защищаемом каталоге
AuthType Basic
AuthName "Restricted area"
AuthUserFile /var/www/example.com/.htpasswd
Require valid-user
Путь в AuthUserFile обязан быть абсолютным — относительный путь даёт 500. Флаг -c у htpasswd создаёт файл заново, стирая прежних пользователей; для добавления второго пользователя запускайте команду без него.
Basic-аутентификация передаёт логин и пароль в заголовке при каждом запросе. Без HTTPS это открытый текст в сети. Ставьте её только на сайте с рабочим сертификатом — проверить его можно на странице проверки SSL. Второй нюанс: если у приложения есть собственная авторизация, оно тоже читает заголовок Authorization, и две системы могут конфликтовать — вплоть до выкидывания из админки.
Служебные файлы стоит закрывать явно, не полагаясь на то, что «о них никто не знает». Типовой набор — конфиги окружения, дампы, логи, бэкапы и каталоги систем контроля версий:
# запретить отдачу служебных файлов
<FilesMatch "^\.|\.(env|ini|log|sql|sql\.gz|bak|old|sh)$">
Require all denied
</FilesMatch>
# выключить листинг каталога, если в нём нет индексного файла
Options -Indexes
# запретить выполнение PHP в каталоге загрузок
<FilesMatch "\.(php|phtml|php[0-9])$">
Require all denied
</FilesMatch>
Последний блок кладут в каталог пользовательских загрузок. Это одна из немногих мер, которая реально останавливает веб-шелл, загруженный через дырявую форму: файл на диск попадёт, но выполнить его через веб не получится. Что делать, если заражение уже случилось — в разборе проверки сайта на вирусы.

Кэширование, сжатие и заголовки ответа
Если у сайта нет доступа к конфигу сервера, .htaccess — единственное место, где можно управлять кэшированием статики и сжатием. Оба механизма дают заметный эффект и оба легко настроить неправильно.
<IfModule mod_deflate.c>
AddOutputFilterByType DEFLATE text/html text/plain text/css text/xml
AddOutputFilterByType DEFLATE application/javascript application/json
AddOutputFilterByType DEFLATE image/svg+xml
</IfModule>
<IfModule mod_expires.c>
ExpiresActive On
ExpiresByType text/css "access plus 1 year"
ExpiresByType application/javascript "access plus 1 year"
ExpiresByType image/webp "access plus 6 months"
ExpiresByType image/svg+xml "access plus 6 months"
ExpiresDefault "access plus 1 hour"
</IfModule>
<IfModule mod_headers.c>
Header set X-Content-Type-Options "nosniff"
Header set Referrer-Policy "strict-origin-when-cross-origin"
</IfModule>
Обёртка IfModule здесь не украшение: без неё отсутствие модуля кладёт весь каталог в 500. Год кэширования безопасен только для файлов с версией в имени или в query-строке — иначе посетители получат старый CSS до истечения срока. HTML долго кэшировать нельзя: контент меняется, а пользователь будет видеть прошлую версию страницы.
Сжимать уже сжатое бессмысленно и вредно: изображения в JPEG, PNG, WebP, видео и архивы тратят процессорное время без выигрыша в размере. Что даёт компрессия на разных типах контента, разобрано в сравнении gzip и Brotli, а стратегии кэша — в материале про кэширование веб-приложений.
ЧПУ и фронт-контроллер: блок CMS, который нельзя править вручную
Почти все CMS используют одну и ту же схему: если запрошенного файла и каталога физически нет на диске, запрос отдаётся входному скрипту, а тот сам разбирает адрес.
# BEGIN WordPress
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteRule ^index\.php$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php [L]
</IfModule>
# END WordPress
Две строки-маркера BEGIN и END — не комментарий для человека, а границы, по которым CMS находит свой блок и перезаписывает его при сохранении настроек постоянных ссылок. Всё, что вы впишете внутрь этих строк, однажды исчезнет без предупреждения. Свои правила добавляйте выше блока (если они должны отработать раньше) или ниже, но никогда не внутри.
Порядок здесь имеет значение: редирект на канонический адрес логично держать выше блока CMS, чтобы он срабатывал до передачи управления входному скрипту.
Ошибка 500 после правки и другие типичные поломки
Главное, что нужно знать про диагностику: apachectl configtest проверяет основной конфиг и не заглядывает в .htaccess. Ошибка в распределённом файле проявляется только в момент запроса и пишется в лог ошибок сайта. Поэтому первый шаг всегда один — открыть лог.
# последние ошибки Apache
tail -n 50 /var/log/apache2/error.log # Debian, Ubuntu
tail -n 50 /var/log/httpd/error_log # RHEL, AlmaLinux, CentOS
# цепочка редиректов и заголовки ответа
curl -sIL https://example.com | grep -i -E '^HTTP/|^location:'
curl -sI https://example.com/assets/style.css | grep -i -E 'cache-control|content-encoding'
Что означают самые частые сообщения:
| Симптом или строка в логе | Причина | Что делать |
|---|---|---|
Invalid command 'RewriteEngine' | Не подключён mod_rewrite | Включить модуль; на общем хостинге — запросить у поддержки |
Invalid command 'php_value' | PHP работает как FPM или CGI, а не модулем Apache | Переносить настройки PHP в .user.ini или в панель хостинга |
.htaccess: Option Indexes not allowed here | Директива вне разрешённых групп AllowOverride | Расширить AllowOverride в конфиге или отказаться от директивы |
| 500 сразу после сохранения, лог пуст | BOM в начале файла или битая кодировка | Пересохранить как UTF-8 без BOM с переводами строк LF |
ERR_TOO_MANY_REDIRECTS | Условие редиректа истинно и после редиректа | Проверять X-Forwarded-Proto за прокси; убрать пересекающиеся правила |
| 403 на весь каталог | Require all denied шире, чем задумано, или нет индексного файла при Options -Indexes | Сузить условие; разбор — в материале про ошибку 403 |
| Правило просто не срабатывает | AllowOverride None, файл не в том каталоге, правило перекрыто более ранним [L] | Проверить каталог и порядок правил |
Если лог не даёт ответа, работает грубый, но быстрый метод: закомментировать половину файла и повторить запрос. Две-три итерации локализуют строку точнее, чем перечитывание конфига глазами. Общий разбор пятисоток — в материале про ошибку 500.
Чем .htaccess платит за удобство
Основной конфиг Apache разбирает один раз при старте и держит в памяти. Распределённые файлы устроены иначе: при AllowOverride, отличном от None, сервер на каждом запросе проверяет наличие файла в каждом каталоге по пути — от корня документов до целевого — и разбирает найденное заново. Для страницы с сотней статических файлов это сотни лишних обращений к файловой системе.
Отсюда практическое правило: если доступ к конфигу виртуального хоста есть, правила нужно переносить туда, а в каталоге ставить AllowOverride None. Это не микрооптимизация ради красоты — на нагруженном сайте разница видна в графике времени ответа. Как искать причину замедления системно, описано в материале про высокую нагрузку на сервер, а разница между тарифами с доступом к конфигу и без — в сравнении хостинга, VPS и выделенного сервера.

Хостинг на nginx: .htaccess не работает — что делать
nginx не поддерживает распределённые файлы конфигурации по своей архитектуре и не будет их поддерживать. Файл в корне сайта на nginx — просто текстовый файл, который никто не читает. Правила переносятся в server-блок и перечитываются командой перезагрузки конфигурации; синтаксис и структура разобраны в руководстве по настройке nginx, а куда смотреть при отладке — в материале про логи nginx.
Отдельно стоит связка, где nginx стоит перед Apache: динамику проксирует Apache, а статику nginx отдаёт сам. В такой схеме правила из .htaccess применяются к PHP-страницам и не применяются к картинкам и стилям — заголовки кэширования для статики придётся задавать в nginx. Понять, что именно перед вами, помогает определение технологий сайта и заголовок Server в ответе.
Как проверить, что правила применились
Правка .htaccess считается сделанной не тогда, когда файл сохранён, а тогда, когда изменение видно в ответе сервера. Быстрый круг проверок:
- Проверка редиректов — вся цепочка переходов с кодами: видно и лишний хоп, и цикл.
- Проверка HTTP-заголовков — применились ли
Cache-Control,Content-Encodingи заголовки безопасности. - Проверка скорости — эффект от сжатия и кэша на реальной загрузке страницы.
- Сканер безопасности — не осталось ли открытых служебных файлов и листинга каталогов.
- Проверка robots.txt — если управляете индексацией заголовком
X-Robots-Tag, сверьте её с правилами файла. - Мониторинг доступности — после правки редиректов имеет смысл убедиться, что сайт отвечает нужным кодом не только у вас.
Проверяйте не только главную. Редирект часто ломает частные случаи: адреса с query-строкой, вложенные каталоги, кириллические пути, страницы с завершающим слешем и без него.
Частые вопросы
Почему .htaccess не работает, хотя файл лежит в корне?
Три причины по частоте: сайт обслуживает nginx, который такие файлы не читает; в конфиге стоит AllowOverride None; файл лежит не в том каталоге — например, на уровень выше корня документов. Проверить первое можно по заголовку Server в ответе, второе — только через поддержку хостинга или конфиг виртуального хоста.
Можно ли использовать .htaccess на nginx?
Нет. Это не вопрос настройки: nginx не читает конфигурацию из каталогов сайта принципиально, ради производительности. Правила нужно переписать в server-блок и перезагрузить конфигурацию.
Как закрыть сайт от индексации через .htaccess?
Директивой Header set X-Robots-Tag "noindex, nofollow" из mod_headers. В отличие от robots.txt, этот способ работает и для файлов, которые не являются HTML: PDF, изображений, дампов. Важно не совмещать его с запретом в robots.txt на тот же адрес — если краулеру запрещено скачивать страницу, он не увидит и заголовок.
Нужен ли .htaccess, если есть доступ к конфигу сервера?
Нет, и лучше без него. Всё, что делает распределённый файл, делает конфиг виртуального хоста — быстрее и с проверкой синтаксиса до перезапуска. Держать .htaccess имеет смысл там, где доступа к конфигу нет или где правила должен менять человек без прав администратора.
Почему после правки редиректа браузер зациклился, а curl показывает норму?
Браузер кэширует ответ 301 надолго, иногда до очистки данных сайта. Проверяйте изменения в приватном окне или инструментом, который не хранит кэш редиректов, и не ставьте 301, пока правило не проверено: заменить его на 302 задним числом сложно — старый ответ уже сохранён у пользователей.
Где лежит .htaccess у WordPress и что будет, если его удалить?
В корне сайта, рядом с wp-config.php. Без него постоянные ссылки перестанут открываться: все адреса, кроме главной, вернут 404, потому что запросы больше не передаются входному скрипту. Файл восстанавливается автоматически при повторном сохранении настроек постоянных ссылок, если у веб-сервера есть права на запись в каталог.
Чеклист правки .htaccess
- Сделана резервная копия файла с датой в имени.
- Файл лежит в корне документов, сохранён в UTF-8 без BOM, права 644.
- Каждый блок с директивами модуля обёрнут в
IfModule. - Редиректы на канонический адрес приводят к цели за один переход.
- За обратным прокси схема определяется по
X-Forwarded-Proto, а не по%{HTTPS}. - Синтаксис контроля доступа соответствует версии Apache (
Requireдля 2.4). - Файл с паролями лежит вне публичного каталога, путь в
AuthUserFileабсолютный. - Служебные файлы и выполнение скриптов в каталоге загрузок закрыты.
- Собственные правила добавлены вне блока
BEGINиENDвашей CMS. - После правки проверены цепочка редиректов, заголовки ответа и несколько нетипичных адресов, а не только главная страница.