.htaccess — файл, в который на боевых проектах слоями оседает всё: редиректы от прошлого подрядчика, правила от сеошника, «защита», скопированная с форума в 2014 году. Разбирать такой файл приходится обычно в момент, когда сайт уже отдаёт 500.
Ниже — разбор того, что действительно нужно в .htaccess для Битрикса, что туда попадает по инерции, и три директивы, которые чаще всего и кладут сайт при переезде на новый сервер.
Сначала проверьте, читается ли ваш .htaccess вообще. На типовой конфигурации BitrixVM перед Apache стоит nginx, и статику — картинки, CSS, JS — он отдаёт сам, не доходя до Apache. Всё, что вы напишете в .htaccess про сжатие и заголовки кэширования для статических файлов, в этом случае просто не выполнится, а вы будете полдня выяснять, почему заголовки не меняются. Проверяется одной командой: curl -I https://site.ru/bitrix/js/main/core/core.js — по заголовку Server видно, кто ответил.
И второе: .htaccess читается Apache при каждом запросе, для каждой директории в пути. Если у вас есть доступ к конфигу виртуального хоста — правила там работают быстрее, а AllowOverride None убирает лишние обращения к диску.
Расположение файла
Файл .htaccess находится в корне сайта. Редактируйте через SFTP/FTP (FileZilla, WinSCP), а не через админку Битрикса.
Важно: Перед изменением сделайте резервную копию файла. Ошибка в .htaccess может привести к 500 ошибке на всём сайте.
Базовая конфигурация
# Кодировка по умолчанию
AddDefaultCharset utf-8
# Отключаем листинг директорий
Options -Indexes
# Отключаем обработку SSI
Options -IncludesNoExec
# Включаем символические ссылки
Options +FollowSymLinks
# Обработка PHP.
# ВНИМАНИЕ: директивы php_flag/php_value понимает только mod_php.
# На PHP-FPM (а это большинство современных серверов) они дают
# 500 Internal Server Error — см. предупреждение ниже
php_flag display_errors off
php_flag allow_url_fopen on
# Защита служебных файлов.
# Синтаксис Apache 2.4; для 2.2 использовался Order/Deny
<Files *.sql>
Require all denied
</Files>
<FilesMatch "\.(log|bak|sql|sql\.gz|tar|zip)$">
Require all denied
</FilesMatch>
# Включаем mod_rewrite
<IfModule mod_rewrite.c>
RewriteEngine On
</IfModule>Три директивы, которые кладут сайт при переезде на новый сервер. Все три дают одинаковый симптом — 500 на всём сайте и запись «Invalid command» в error.log Apache.
php_flag и php_value. Работают только с mod_php. На PHP-FPM Apache их не понимает. Настройки PHP в этом случае задаются в пуле FPM или через .user.ini в корне сайта.
Order, Deny from, Allow from. Это синтаксис Apache 2.2. В 2.4 он поддерживается только при подключённом mod_access_compat, которого в минимальных сборках нет. Современный эквивалент — Require all denied / Require all granted.
Options +FollowSymLinks. Если хостер запретил переопределять Options через AllowOverride, эта строка тоже даёт 500. При проблемах пробуйте Options +SymLinksIfOwnerMatch.
Отсюда практическое правило: после любой правки .htaccess первым делом смотрите не на сайт, а в error.log Apache — там будет точная строка и номер.
Редиректы
1. С www на без www (или наоборот)
# С www на без www
RewriteCond %{HTTP_HOST} ^www\.(.+)$ [NC]
RewriteRule ^(.*)$ https://%1/$1 [R=301,L]
# Или с без www на www
# RewriteCond %{HTTP_HOST} !^www\. [NC]
# RewriteRule ^(.*)$ https://www.%{HTTP_HOST}/$1 [R=301,L]2. С HTTP на HTTPS
RewriteCond %{HTTPS} off
RewriteCond %{HTTP:X-Forwarded-Proto} !https
RewriteRule ^(.*)$ https://%{HTTP_HOST}/$1 [R=301,L]3. Удаление index.php
RewriteCond %{THE_REQUEST} ^[A-Z]{3,9}\ /(.*)index\.php($|\ |\?)
RewriteRule ^(.*)index\.php$ /$1 [R=301,L]4. Добавление слэша в конце URL
# Только для директорий, не для файлов
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_URI} !/$
RewriteCond %{REQUEST_URI} !\.[a-zA-Z0-9]{1,5}$
RewriteRule ^(.*)$ $1/ [R=301,L]5. Удаление дублирующихся слэшей
RewriteCond %{THE_REQUEST} //
RewriteRule ^.*$ /$0 [R=301,L]Проверяйте цепочки редиректов, а не отдельные правила. Каждое из правил выше по отдельности корректно, но вместе они складываются: запрос на http://www.site.ru/catalog/index.php пройдёт через убирание www, переход на HTTPS, удаление index.php и добавление слэша — четыре последовательных 301. Для пользователя это лишние круги, для поисковика — размытие сигнала, а на длинной цепочке часть роботов просто перестаёт идти дальше.
Проверять нужно так: curl -sIL http://www.site.ru/catalog/index.php | grep -E "^(HTTP|Location)". Идеал — один редирект на конечный адрес; порядок правил в файле для этого важнее их содержимого.
ЧПУ для Битрикс
<IfModule mod_rewrite.c>
RewriteEngine On
# Базовая директория
RewriteBase /
# Если файл или директория существует — отдаём как есть
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-l
# Иначе передаём в Битрикс
RewriteRule ^(.*)$ /bitrix/urlrewrite.php [L]
</IfModule>Сжатие (mod_deflate)
<IfModule mod_deflate.c>
# Сжимаем текстовые файлы
AddOutputFilterByType DEFLATE text/html
AddOutputFilterByType DEFLATE text/plain
AddOutputFilterByType DEFLATE text/xml
AddOutputFilterByType DEFLATE text/css
AddOutputFilterByType DEFLATE text/javascript
AddOutputFilterByType DEFLATE application/javascript
AddOutputFilterByType DEFLATE application/x-javascript
AddOutputFilterByType DEFLATE application/json
AddOutputFilterByType DEFLATE application/xml
AddOutputFilterByType DEFLATE application/rss+xml
AddOutputFilterByType DEFLATE image/svg+xml
# Исключаем браузеры с проблемами
BrowserMatch ^Mozilla/4 gzip-only-text/html
BrowserMatch ^Mozilla/4\.0[678] no-gzip
BrowserMatch \bMSIE !no-gzip !gzip-only-text/html
</IfModule>Кэширование браузера
<IfModule mod_expires.c>
ExpiresActive On
ExpiresDefault "access plus 1 month"
# HTML — без кэша
ExpiresByType text/html "access plus 0 seconds"
# Данные — без кэша
ExpiresByType application/json "access plus 0 seconds"
ExpiresByType application/xml "access plus 0 seconds"
# RSS/Atom
ExpiresByType application/rss+xml "access plus 1 hour"
ExpiresByType application/atom+xml "access plus 1 hour"
# Favicon
ExpiresByType image/x-icon "access plus 1 week"
ExpiresByType image/vnd.microsoft.icon "access plus 1 week"
# Медиа
ExpiresByType image/jpeg "access plus 1 year"
ExpiresByType image/png "access plus 1 year"
ExpiresByType image/gif "access plus 1 year"
ExpiresByType image/webp "access plus 1 year"
ExpiresByType image/svg+xml "access plus 1 month"
ExpiresByType video/mp4 "access plus 1 year"
ExpiresByType audio/mp3 "access plus 1 year"
# Шрифты
ExpiresByType font/woff "access plus 1 year"
ExpiresByType font/woff2 "access plus 1 year"
ExpiresByType application/font-woff "access plus 1 year"
ExpiresByType application/font-woff2 "access plus 1 year"
# CSS и JS
ExpiresByType text/css "access plus 1 year"
ExpiresByType application/javascript "access plus 1 year"
ExpiresByType text/javascript "access plus 1 year"
</IfModule>
# Cache-Control заголовки
<IfModule mod_headers.c>
# Статика — долгий кэш
<FilesMatch "\.(ico|jpg|jpeg|png|gif|webp|svg|woff|woff2|css|js)$">
Header set Cache-Control "public, max-age=31536000, immutable"
</FilesMatch>
# HTML — без кэша
<FilesMatch "\.(html|htm|php)$">
Header set Cache-Control "no-cache, no-store, must-revalidate"
</FilesMatch>
</IfModule>Защита от атак
Раздел, который стоит читать критически: большая часть «защиты» в типовых .htaccess даёт больше вреда, чем пользы. Разберу конкретно, что с ней не так, — но сначала сам код, потому что вы его всё равно встретите на любом унаследованном проекте.
# Блокировка сканеров уязвимостей
RewriteCond %{QUERY_STRING} (\<|%3C).*script.*(\>|%3E) [NC,OR]
RewriteCond %{QUERY_STRING} GLOBALS(=|\[|\%[0-9A-Z]{0,2}) [OR]
RewriteCond %{QUERY_STRING} _REQUEST(=|\[|\%[0-9A-Z]{0,2})
RewriteRule .* - [F,L]
# Защита от SQL-инъекций
RewriteCond %{QUERY_STRING} union.*select.*\( [NC,OR]
RewriteCond %{QUERY_STRING} concat.*\( [NC]
RewriteRule .* - [F,L]
# Блокировка подозрительных User-Agent
<IfModule mod_setenvif.c>
SetEnvIfNoCase User-Agent "^$" bad_bot
SetEnvIfNoCase User-Agent "wget" bad_bot
SetEnvIfNoCase User-Agent "curl" bad_bot
SetEnvIfNoCase User-Agent "HTTrack" bad_bot
<Limit GET POST HEAD>
Order Allow,Deny
Allow from all
Deny from env=bad_bot
</Limit>
</IfModule>Что не так с этим блоком.
Фильтрация SQL-инъекций по QUERY_STRING — это имитация WAF, которая не останавливает настоящую атаку (обходится кодированием и регистром) и при этом ломает нормальные запросы. Поиск по сайту со словом «select», товар с «concat(» в названии, любой параметр, содержащий подстроку _REQUEST, — всё это получит 403. Защита от инъекций находится в коде, в подготовленных запросах, а не в регулярке на входе.
Блокировка curl и wget по User-Agent — самый вредный пункт списка. Настоящий сканер подставит Mozilla/5.0 первой же строкой конфига. А под запрет попадёте вы сами: мониторинг доступности, health-check, вебхуки платёжных систем и служб доставки, интеграции по API, ваш собственный curl -I для проверки редиректов. Разбор инцидента «платежи перестали подтверждаться» с такой строкой в .htaccess занимает часы, потому что искать причину идут в код оплаты.
Блокировка пустого User-Agent — по тем же причинам: часть легитимных серверных клиентов его не отправляет.
Что действительно работает вместо этого: ограничение частоты запросов на уровне nginx или Apache, актуальные версии CMS и модулей, и — если нужен именно WAF — отдельный инструмент вроде ModSecurity с поддерживаемым набором правил, а не десять строк регулярок.
Полная конфигурация для production
# ============================================
# .htaccess для 1С-Битрикс
# ============================================
# Кодировка
AddDefaultCharset utf-8
# Безопасность
Options -Indexes -IncludesNoExec +FollowSymLinks
php_flag display_errors off
<Files .htaccess>
Order allow,deny
Deny from all
</Files>
# ============================================
# Редиректы и ЧПУ
# ============================================
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
# HTTPS
RewriteCond %{HTTPS} off
RewriteCond %{HTTP:X-Forwarded-Proto} !https
RewriteRule ^(.*)$ https://%{HTTP_HOST}/$1 [R=301,L]
# www -> без www
RewriteCond %{HTTP_HOST} ^www\.(.+)$ [NC]
RewriteRule ^(.*)$ https://%1/$1 [R=301,L]
# Удаление index.php
RewriteCond %{THE_REQUEST} ^[A-Z]{3,9}\ /(.*)index\.php($|\ |\?)
RewriteRule ^(.*)index\.php$ /$1 [R=301,L]
# Trailing slash
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_URI} !/$
RewriteCond %{REQUEST_URI} !\.[a-zA-Z0-9]{1,5}$
RewriteRule ^(.*)$ $1/ [R=301,L]
# ЧПУ Битрикс
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-l
RewriteRule ^(.*)$ /bitrix/urlrewrite.php [L]
</IfModule>
# ============================================
# Сжатие
# ============================================
<IfModule mod_deflate.c>
AddOutputFilterByType DEFLATE text/html text/plain text/xml
AddOutputFilterByType DEFLATE text/css text/javascript
AddOutputFilterByType DEFLATE application/javascript application/json
AddOutputFilterByType DEFLATE application/xml image/svg+xml
</IfModule>
# ============================================
# Кэширование
# ============================================
<IfModule mod_expires.c>
ExpiresActive On
ExpiresDefault "access plus 1 month"
ExpiresByType text/html "access plus 0 seconds"
ExpiresByType image/jpeg "access plus 1 year"
ExpiresByType image/png "access plus 1 year"
ExpiresByType image/webp "access plus 1 year"
ExpiresByType text/css "access plus 1 year"
ExpiresByType application/javascript "access plus 1 year"
ExpiresByType font/woff2 "access plus 1 year"
</IfModule>Год кэширования для CSS и JS требует версионирования. ExpiresByType text/css "access plus 1 year" означает, что браузер вернувшегося пользователя не станет перезапрашивать файл — со всеми последствиями после выкладки новой вёрстки. Работает это только если в адресе файла меняется что-то при каждом изменении содержимого: style.css?v=hash или имя с хэшем. Собственные CSS и JS шаблона такого версионирования обычно не имеют — для них год слишком много, разумнее месяц или явный параметр версии в шаблоне.
Итоги
Здоровый .htaccess для Битрикса — это редиректы (по одному правилу на каждый вид дублей), правило ЧПУ и, если запросы до Apache действительно доходят, сжатие с кэшированием. Всё остальное, что обычно в нём лежит, стоит пересмотреть.
Порядок работы, который экономит время:
- Убедитесь, что файл вообще применяется. На связке nginx + Apache половина правил не работает для статики.
- Проверяйте цепочки редиректов, а не отдельные правила:
curl -sIL. - После правки — сразу в
error.logApache. Три четверти пятисоток по теме объясняются одной строкой в логе. - Не держите «защиту» из регулярок. Она не останавливает атакующего и ломает свои же интеграции.
- Если есть доступ к конфигу виртуального хоста — переносите правила туда. Быстрее и не читается на каждый запрос.
Перед выкладкой сохраните работающую копию файла рядом (.htaccess.bak) и проверьте результат из другого браузера или приватного окна — кэш редиректов на стороне браузера сам по себе способен убедить вас в том, что правило работает не так, как есть на самом деле. curl от этого свободен, поэтому проверять лучше им.
Комментарии
Система комментариев скоро будет подключена. А пока вы можете написать мне в Telegram или на email.