Зачем избавляться от .htaccess
.htaccess исторически решает задачи делегирования прав: веб‑приложения и пользователи могут менять поведение Apache без доступа к глобальной конфигурации. Цена за гибкость — накладные расходы на каждый запрос и размытая управляемость. При включённом AllowOverride Apache проверяет наличие .htaccess во всех директориях пути к запрашиваемому файлу. Это вызывает дополнительные системные вызовы и «слипания» правил из разных уровней (директория, поддиректория), что усложняет отладку и повышает шанс ошибки конфигурации.
На реальных проектах отключение .htaccess и перенос правил в контекст VirtualHost даёт заметный выигрыш: минус 3–10 системных вызовов на статический запрос, более предсказуемое поведение mod_rewrite и централизованное управление безопасностью. В типичных CMS прирост 5–20% по RPS для статики и 2–10% по p95 для динамики (цифры зависят от диска, числа уровней каталогов и набора модулей).
Простой критерий: если вы можете править конфигурацию Apache и перезапускать службу — .htaccess вам не нужен. Отключите его и переносите логику в vhost.
Когда .htaccess всё ещё оправдан
Только когда нет доступа к конфигурации сервера: мультиарендный хостинг с изоляцией пользователей (типичный виртуальный хостинг), учебные стенды, редкие сценарии, где нельзя перезапустить Apache. В любом другом случае — переносим правила и отключаем AllowOverride. Нужен полный контроль конфигурации и модулей — переносите проект на VDS.
Краткий план миграции
- Инвентаризация: собрать все .htaccess в
DocumentRootи подкаталогах. - Проверить необходимые модули: mod_rewrite, mod_headers, mod_expires, mod_access_compat или современные модули авторизации.
- Перенести правила в
VirtualHostс корректировкой синтаксиса под нужный контекст. - Включить централизованные политики безопасности и кеширования.
- Прогнать проверки:
configtest, тест‑запросы, логи. - Отключить
AllowOverride(None) и удалить/заархивировать .htaccess. - Мониторинг после релиза: ошибки, 404/403, производительность.
Контекст имеет значение: как меняется синтаксис
Главная ловушка при переносе — различия контекста mod_rewrite:
- .htaccess и
<Directory>(per-dir): шаблоныRewriteRuleматчат путь без ведущего слэша, уже «обрезанный» текущей директорией.RewriteBaseможет использоваться для относительных подстановок, но в<Directory>обычно не нужен. - Контекст сервера/
VirtualHost(вне<Directory>): шаблоны матчат полный URL‑путь с ведущим/относительноDocumentRoot.
Отсюда практические правила:
- Если пишете
RewriteRuleпрямо вVirtualHost, добавляйте ведущий/в шаблоны. - Если размещаете логику внутри
<Directory>(частый и удобный вариант) — используйте пер‑директориальную семантику: без ведущего слэша,RewriteBaseне нужен. - Явно выключайте
MultiViews, чтобы mod_negotiation не подменял 404.
Проверка модулей и конфигурации
apachectl -M
apachectl -t
apachectl -S
Первой командой убедитесь, что необходимый модуль включён. Второй — валидируйте синтаксис, третьей — проверьте, что правите нужный vhost.

Перенос типичных правил: WordPress и похожие CMS
Классические правила WordPress в .htaccess:
# .htaccess в DocumentRoot
RewriteEngine On
RewriteBase /
RewriteRule ^index\.php$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php [L]
Эквивалент в vhost внутри <Directory>. Обратите внимание: без ведущего слэша и без RewriteBase:
# Внутри файла сайта с vhost
<VirtualHost *:80>
ServerName example.com
DocumentRoot /var/www/example.com/public
<Directory /var/www/example.com/public>
AllowOverride None
Options -Indexes -MultiViews
Require all granted
RewriteEngine On
RewriteRule ^index\.php$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . index.php [L]
</Directory>
</VirtualHost>
Альтернатива — разместить правила прямо в VirtualHost вне <Directory>, тогда шаблоны должны начинаться с /.
Laravel/Symfony: перенаправление через public/index.php
Перенос из .htaccess в vhost для приложений с фронт‑контроллером:
<VirtualHost *:80>
ServerName app.local
DocumentRoot /var/www/app/current/public
<Directory /var/www/app/current/public>
AllowOverride None
Options -Indexes -MultiViews
Require all granted
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . index.php [L]
</Directory>
<Directory /var/www/app/current>
# Безопасность: запрет на .env, ключи и служебные каталоги
<FilesMatch "^\.env|composer\.(json|lock)$">
Require all denied
</FilesMatch>
<DirectoryMatch ".*/(storage|bootstrap/cache|vendor)">
Require all denied
</DirectoryMatch>
</Directory>
</VirtualHost>
Суть та же: AllowOverride None, чёткие границы <Directory>, защита служебных файлов и директорий.
Политики безопасности в vhost вместо .htaccess
Вместо разрозненных директив в .htaccess удобно задать централизованные правила:
- Запрет доступа к скрытым файлам и VCS:
.git,.hg,.svn, резервные файлы редакторов. - Перекрытие каталожного листинга:
Options -Indexes. - Защита конфигов и секретов:
.env, ключи, дампы. - Жёсткая схема обработки PHP: трафик только через фронт‑контроллер, нет возможности выполнять PHP в каталогах загрузок.
<VirtualHost *:80>
ServerName secure.example
DocumentRoot /var/www/secure/public
<Directory /var/www/secure/public>
AllowOverride None
Options -Indexes -ExecCGI -Includes
Require all granted
<FilesMatch "^\.(?!well-known).*">
Require all denied
</FilesMatch>
<FilesMatch "\.(git|hg|svn|bak|swp|old)$">
Require all denied
</FilesMatch>
</Directory>
<Directory /var/www/secure>
<FilesMatch "^(\.env|.*\.key|.*\.pem|composer\.(json|lock))$">
Require all denied
</FilesMatch>
</Directory>
</VirtualHost>
Дополнительно продумайте заголовки безопасности: Content-Security-Policy, Referrer-Policy, X-Content-Type-Options, Permissions-Policy. Подробно это разобрано в материале «гайд по заголовкам безопасности для Nginx и Apache».
Кеширование и статические ресурсы
Перенос директив кеширования из .htaccess в vhost даёт выигрыш в читаемости и исключает противоречия между подкаталогами. Базовый пример:
<VirtualHost *:80>
ServerName static.example
DocumentRoot /var/www/static
<Directory /var/www/static>
AllowOverride None
Require all granted
</Directory>
ExpiresActive On
ExpiresDefault "access plus 1h"
<FilesMatch "\.(ico|css|js|gif|jpe?g|png|svg|webp|avif)$">
ExpiresDefault "access plus 30d"
Header set Cache-Control "public, max-age=2592000, immutable"
</FilesMatch>
<FilesMatch "\.(html)$">
Header set Cache-Control "no-cache"
</FilesMatch>
</VirtualHost>
Синхронизируйте версионирование файлов (суффиксы, хэши), чтобы «длинный» кеш не мешал выкатывать обновления. Дополнительно см. «стратегии Cache-Control и ETag для статики».

Производительность: что ещё учесть при отказе от .htaccess
Миграция — хороший момент посмотреть вокруг и убрать исторические узкие места:
- MPM и PHP: предпочтительнее связка event + PHP‑FPM вместо prefork + mod_php.
- Сжатие:
mod_deflateили brotli‑модуль, включённые в vhost. - HTTP/2: если используется TLS, проверьте, что сайт обслуживается с
Protocols h2 http/1.1. - KeepAlive и таймауты: удостоверьтесь, что глобальные значения соответствуют нагрузке и профилю клиентов.
- Логи: разнесите access/error по сайтам для удобной диагностики после миграции.
Переезд без простоя: пошагово
Рекомендуемый рабочий процесс для администратора:
- Собрать и сгруппировать директивы из всех .htaccess. Пометьте дубликаты и конфликтующие правила.
- Подготовить новый файл конфигурации сайта в каталоге sites-available (или аналогичном). Используйте отдельный блок
<Directory>подDocumentRoot. - Перенести rewrite, доступ и заголовки, адаптировав синтаксис под контекст vhost.
- Прогнать
apachectl -tи включить сайт. Мягкая перезагрузкаgracefulминимизирует прерывания запросов. - Протестировать основные маршруты и загрузку статики. Проверить 404/403, редиректы, коды кеша.
- Включить детализированный лог mod_rewrite на время проверки.
- Когда всё совпадает — ставим
AllowOverride Noneи удаляем .htaccess (или архивируем).
apachectl -t
apachectl -S
apachectl graceful
Как включить трассировку mod_rewrite на время миграции
# Внутри нужного vhost
LogLevel warn rewrite:trace2
Не забывайте вернуть уровень логирования назад после проверки, чтобы не «шуметь» и не раздувать логи.
Частые ловушки и как их избежать
- Неправильный контекст: в
<Directory>шаблоны без ведущего слэша; вVirtualHost(вне<Directory>) — с ведущим/. - Оставили
AllowOverrideне равнымNone: Apache продолжит читать .htaccess, и вы получите двусмысленность конфигурации. - Незадекларированный модуль: при миграции директив
HeaderилиExpiresубедитесь, что модули подключены. - Исполняемый PHP в каталогах загрузок: запретите обработку PHP вне фронт‑контроллера.
MultiViewsмаскирует 404: явно выключайте при использовании mod_rewrite.
Частичный отказ: AllowOverrideList
Если по процессным причинам нужно временно оставить часть возможностей .htaccess, используйте «белый список» директив:
<Directory /var/www/site/public>
AllowOverrideList Redirect RewriteEngine RewriteCond RewriteRule
</Directory>
Это лучше, чем общий AllowOverride All, но конечная цель — AllowOverride None и чистая конфигурация в vhost.
Проверочный чек‑лист перед отключением .htaccess
- Главные маршруты приложения (без и с параметрами) отрабатывают корректно.
- Редиректы: домен без www/с www, слэш в конце, HTTP→HTTPS, каноникал — все даются нужными кодами и без циклов.
- Статика: заголовки кеша, сжатие, типы контента корректны.
- Запрещённые файлы недоступны: .env, .git, резервные.
- Журналы чистые: нет массовых 404/403/500, нет неожиданных переписей.
- Нагрузочные тесты показывают ожидаемую или лучшую производительность.
Перед включением редиректа на HTTPS убедитесь, что установлены корректные SSL-сертификаты.
Итоги
Отказ от .htaccess и перенос всей логики в VirtualHost — это про производительность, безопасность и управляемость. Вы перестаёте платить за проверку конфигурации на каждый запрос, упрощаете отладку и централизуете политику доступа. Взамен — один раз продуманная миграция с корректной адаптацией контекста mod_rewrite, тестами и мониторингом. В долгосрочной перспективе это даёт меньше сюрпризов и больше RPS.
Если вы администрируете один сервер или парк проектов, заведите единые шаблоны vhost под типовые стеки (CMS, фреймворки, статика) и используйте инклюды: перенос станет повторяемым и безопасным, а .htaccess больше не будет скрытым источником задержек и уязвимостей.


