Если сайт живёт на виртуальном хостинге, у вас, как правило, нет доступа к конфигурации веб‑сервера на уровне vhost, но почти всегда доступен .htaccess. От него напрямую зависят безопасность и предсказуемость поведения приложений (WordPress, Laravel, Yii и т.д.). Ниже — рабочие практики «защиты по умолчанию»: закрываем .env и .git, отключаем индексирование каталогов, запрещаем исполнение PHP в uploads, разбираем подводные камни и совместимость Apache 2.4/2.2.
Почему именно .env, .git и uploads
Три самых частых источника проблем на виртуальном хостинге:
- .env — файл с секретами (пароли к БД, ключи API). Если он доступен по HTTP — это мгновенная компрометация проекта.
- .git — служебный каталог системы контроля версий; при публичном доступе злоумышленник может восстановить исходники и историю коммитов.
- uploads — каталог с загружаемыми пользователями файлами (медиа). Сюда часто пытаются подсунуть web‑shell под видом картинки: задача — запретить исполнение любых скриптов тут.
Критерий успеха: попытки обратиться к .env, .git или любым «php» в uploads должны давать 403/404 и не раскрывать содержимого. Плюс отключён листинг каталогов.
Что можно и чего нельзя в .htaccess на виртуальном хостинге
В разных тарифах AllowOverride может отличаться. Где‑то можно менять Options и использовать mod_rewrite, где‑то нет. Если сомневаетесь, включайте защиту по слоям: FilesMatch с Require all denied (Apache 2.4) и резервно правила на mod_rewrite, чтобы блокировать каталоги вроде .git. В Apache 2.4 используется директива Require, в старых конфигурациях 2.2 — Deny/Allow. Мы дадим обе версии, обернув их в <IfModule>. Если у вас свой сервер на VDS, лучше уносить эти правила в конфиг vhost — см. разбор в статье почему конфиг vhost предпочтительнее .htaccess.

Базовый каркас .htaccess в корне сайта
Ниже — минимальный набор, который стоит держать первым делом. Он выключает листинг каталогов (Options -Indexes), запрещает доступ к .env, скрытым файлам/служебным каталогам, блокирует бэкапы и защищает служебные .ht*.
# 1) Общая гигиена: без индексации каталогов, без контент-неготиации по имени
Options -Indexes
Options -MultiViews
# 2) Защитить служебные .ht* (включая .htaccess, .htpasswd)
<FilesMatch "^\.ht">
<IfModule mod_authz_core.c>
Require all denied
</IfModule>
<IfModule !mod_authz_core.c>
Order allow,deny
Deny from all
</IfModule>
</FilesMatch>
# 3) Закрыть .env (включая вариации вроде .env.local)
<FilesMatch "^\.env(\..*)?$">
<IfModule mod_authz_core.c>
Require all denied
</IfModule>
<IfModule !mod_authz_core.c>
Order allow,deny
Deny from all
</IfModule>
</FilesMatch>
# 4) Блокировать общие артефакты: composer.* и различные бэкапы
<FilesMatch "^(composer\.(json|lock)|package(-lock)?\.json|yarn\.lock|pnpm-lock\.yaml|.*\.(bak|old|swp|swo|tmp|sql|sqlite|zip|tar|gz))$">
<IfModule mod_authz_core.c>
Require all denied
</IfModule>
<IfModule !mod_authz_core.c>
Order allow,deny
Deny from all
</IfModule>
</FilesMatch>
# 5) Разрешить .well-known (ACME) при блокировке скрытых путей
# Важно: сначала разрешаем исключение, потом общую блокировку
<IfModule mod_rewrite.c>
RewriteEngine On
# Разрешить доступ к .well-known
RewriteRule ^\.well-known/ - [L]
# Запретить доступ к скрытым файлам и каталогам (начинаются с точки)
RewriteRule (^|/)\.[^/]+ - [F]
</IfModule>
# 6) Специально блокируем .git (каталог и любые обращения внутрь)
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteRule (^|/)\.git - [F]
</IfModule>
Пояснения:
Options -Indexes— при запросе каталога безindex.php|htmlсервер не отдаст список файлов.Options -MultiViews— отключаем «умную» подстановку по части имени файла, чтобы исключить неожиданные совпадения и утечки.<FilesMatch>удобен для точечного запрета файлов по имени/паттерну.mod_rewriteпригодится для блокировки каталогов типа.git, на которые<FilesMatch>не всегда сработает.- Исключение
.well-knownнужно для ACME и других политик. Если вы выпускаете сертификаты, держите его открытым. Для выпуска можно использовать наши SSL-сертификаты.
Запрещаем выполнение PHP/CGI в uploads
Самая уязвимая точка — каталог загрузок. Название может отличаться: uploads (WordPress), storage, userfiles, assets и т.д. Принцип: внутри этого каталога вообще не должны исполняться сценарии. Даже если злоумышленник загрузил image.php, доступ к нему должен дать 403.
Создайте отдельный .htaccess в каталоге uploads (или аналогичном) со следующим содержимым:
# Отключаем индексирование и выполнение CGI
Options -Indexes
Options -ExecCGI
# Снимаем обработчики с расширений, чтобы ничего не исполнялось
RemoveHandler .php .phtml .php3 .php4 .php5 .php7 .php8 .phar .pl .py .cgi
# Универсальный запрет на доступ к любым «php-подобным» файлам
<FilesMatch "\.(php|php[0-9]|phtml|phpt|phar)$">
<IfModule mod_authz_core.c>
Require all denied
</IfModule>
<IfModule !mod_authz_core.c>
Order allow,deny
Deny from all
</IfModule>
</FilesMatch>
# На случай двоичного исполнения через CGI или другие обработчики
<FilesMatch "\.(pl|py|cgi|asp|aspx|jsp)$">
<IfModule mod_authz_core.c>
Require all denied
</IfModule>
<IfModule !mod_authz_core.c>
Order allow,deny
Deny from all
</IfModule>
</FilesMatch>
# Часто злоумышленники прячут код в .php.xxx или image.jpg.php
<IfModule mod_rewrite.c>
RewriteEngine On
# Если внутри имени встречается .php где угодно — запрещаем
RewriteCond %{REQUEST_URI} \.php [NC]
RewriteRule . - [F]
</IfModule>
Проверка на «двойные расширения» и маскировку
Вредонос может называться avatar.jpg.php или f0o.phar.jpg. Проверка по окончанию строки не всегда спасает, поэтому добавили правило на любую подстроку .php в запросе. Если ваш движок генерирует легитимные ссылки с таким шаблоном (редко), сузьте правило под свои реалии, но лучше разберитесь, почему так происходит. Кстати, директивы php_flag и php_value из .htaccess не работают с PHP‑FPM — пользуйтесь настройкой PHP через user.ini.
Совместимость с Apache 2.4 и 2.2
Современные площадки используют Apache 2.4 и директиву Require. На старых конфигурациях в ходу Deny/Allow. Мы применяем условные блоки:
<IfModule mod_authz_core.c>
Require all denied
</IfModule>
<IfModule !mod_authz_core.c>
Order allow,deny
Deny from all
</IfModule>
Такой шаблон годится для <Files>/<FilesMatch> и не зависит от того, как обрабатывается PHP (mod_php, FPM через proxy_fcgi и т.п.).
Где размещать фрагменты и как не сломать движок
- Базовые правила (защита .env/.git, Options -Indexes, блок бэкапов) — в корневом .htaccess сайта и раньше любых «магистральных» правил CMS.
- Правила для
uploads— отдельный .htaccess внутри каталога загрузок. Не мешайте их с общими rewrite‑правилами движка — так проще сопровождать. - Если у вас WordPress, блок для
uploadsкладите вwp-content/uploads/.htaccess. Кэш‑плагины могут генерировать свои директивы — держите «безопасность» сверху файла. - Если движок сам создаёт .htaccess, наши секции размещайте вне подписанных блоков CMS, чтобы обновления не затирали защиту.
Фильтр на «скрытые» и служебные пути: тонкости
Типичная ловушка — слишком агрессивная блокировка всех путей, начинающихся с точки. Держите исключение для .well-known выше общего запрета. Если вы используете дополнительные интеграции, которые опираются на .well-known (Webfinger, security.txt, Apple/Android‑ассоциации), они продолжат работать.

Блокируем доступ к служебным каталогам проекта
Если вы развернули PHP‑фреймворк в корне веб‑директории (а не в стиле «public/»), стоит заблокировать доступ к vendor, config, storage и прочим внутренностям. Делать это нужно, понимая логику вашего приложения.
# Аккуратно запрещаем прямой доступ к vendor и другим служебным папкам
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteRule ^vendor/ - [F]
RewriteRule ^(config|storage|database|resources|tests)/ - [F]
</IfModule>
Важно: некоторые приложения сами роутят запросы в эти каталоги (например, для отдачи публичных ассетов из подкаталога resources). В таких случаях не применяйте «широкие» запреты без учёта реального роутинга. Если у вас есть доступ к конфигам сервера (см. статью про vhost вместо .htaccess), лучше ограничивайте доступ на уровне vhost.
Дополнительные защитные приёмы для uploads
- Можно отдавать потенциально опасные расширения как текст, но надёжнее возвращать 403, а не «раскрывать» содержимое.
- Периодически чистите «подозрительные» расширения в uploads:
.php,.phar,.phtml,.cgi,.pl. Лучше не допускать их появления вовсе (валидируйте расширения при загрузке). - Проверяйте реальный MIME, а не только расширение. Даже при правильной валидации .htaccess‑блок в uploads обязателен.
Проверка конфигурации: быстрый чеклист
После правок — проверяем заголовки ответов и коды статусов. Подставьте ваш домен и выполните:
curl -I https://example.tld/.env
curl -I https://example.tld/.git/config
curl -I https://example.tld/.htaccess
curl -I https://example.tld/wp-content/uploads/test.php
curl -I https://example.tld/wp-content/uploads/avatar.jpg.php
curl -I https://example.tld/.well-known/acme-challenge/test
Ожидаемое поведение:
/.env,/.git/*,/.ht*, любые*.phpвuploads— 403 (или 404 при «искусственном 404»)./.well-known/*— 200, если ресурс существует (не блокируем ACME и другие политики).- Запрос каталога без index при включённом
Options -Indexes— 403.
Типичные ошибки и как их избежать
- Ставить защитные блоки после «главного» rewrite CMS. На внутренних перезапросах правила могут не сработать. Держите безопасность сверху.
- Полагаться только на
php_flag engine Off. В PHP‑FPM это не работает из .htaccess. ИспользуйтеFilesMatch,RemoveHandler,Options -ExecCGI,mod_rewrite. - Блокировать все скрытые пути без исключений. Обязательно белый список для
.well-known. - Оставлять
uploadsбез отдельного .htaccess. Даже если корневой .htaccess «закрыл всё», локальные правила в каталоге загрузок — последний рубеж. - Забывать про «двойные расширения». Проверяйте не только окончание имени, но и наличие подстроки
.phpв любом месте пути.
Минимальный «комбо»-пример для быстрого старта
Если хотите быстро закрыть основные риски в корне сайта, используйте этот компактный блок. Он объединяет ключевые приёмы: deny для .env/.git, защита .ht*, выключение индексации, запрет на скрытые пути с исключением для .well-known.
Options -Indexes
Options -MultiViews
# Служебные .ht*
<FilesMatch "^\.ht">
<IfModule mod_authz_core.c>
Require all denied
</IfModule>
<IfModule !mod_authz_core.c>
Order allow,deny
Deny from all
</IfModule>
</FilesMatch>
# .env и артефакты сборки/бэкапы
<FilesMatch "^(\.env(\..*)?|composer\.(json|lock)|package(-lock)?\.json|.*\.(bak|old|swp|swo|tmp|sql|sqlite|zip|tar|gz))$">
<IfModule mod_authz_core.c>
Require all denied
</IfModule>
<IfModule !mod_authz_core.c>
Order allow,deny
Deny from all
</IfModule>
</FilesMatch>
# Разрешаем .well-known, остальное «скрытое» запрещаем
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteRule ^\.well-known/ - [L]
RewriteRule (^|/)\.[^/]+ - [F]
RewriteRule (^|/)\.git - [F]
</IfModule>
И не забудьте отдельный .htaccess внутри каталога uploads с запретом выполнения, как показано выше.
Нюансы производительности
.htaccess обрабатывается на каждый запрос, но безопасность важнее. Если у вас высокая нагрузка, оптимизируйте порядок правил: сначала разрешения (например, для .well-known), затем самые «часто совпадающие» запреты, и в конце — крупные регулярки. Для обычного сайта накладные расходы микроскопические по сравнению с риском утечки .env.
Контроль качества: чек‑лист админа
- .env/.git/.ht* недоступны извне.
- Листинг каталогов выключен (
Options -Indexes). - В uploads запрещено исполнение любых сценариев, включая маскировку через двойные расширения.
- .well-known доступен.
- Защита не мешает статическим файлам и роутингу приложения.
Итоги
.htaccess — «брандмауэр ближнего боя» на виртуальном хостинге. Он не заменяет обновления CMS и плагинов, бэкапы и мониторинг, но снимает самые болезненные риски: утечки .env, раскрытие .git и запуск шеллов из uploads. Включайте защиту слоями: deny через FilesMatch, запреты на каталоги через mod_rewrite и здравую валидацию загрузок. Если только запускаете проект — начните с базовой гигиены и при необходимости масштабируйтесь на VDS.


