Ускорение сайта через .htaccess

Файлом .htaccess на Apache можно заметно ускорить сайт без правки кода: настроить кэш статики в браузере, включить сжатие текстовых ответов, отдавать заранее сжатые файлы, подсказать браузеру, что грузить вперёд. Ниже — готовые блоки под каждую задачу: вставляйте в .htaccess в корне сайта, плейсхолдеры (пути, домены) замените на свои. При сомнениях прогоните файл через линтер /check/; собрать .htaccess из чекбоксов можно в генераторе. В конце — чеклист на 5 минут.

Главное правило производительности на уровне .htaccess: кэшировать статику надолго (но с версионированными именами), сжимать текст (но не уже сжатые форматы) и не тащить в .htaccess то, что относится к конфигу сервера (KeepAlive и прочее — см. раздел 6).

1. Кэш статики в браузере

При повторных визитах файлы берутся из локального кэша браузера — ноль запросов к серверу. Два механизма: mod_expires (просто, по MIME-типам) и Header set Cache-Control (гибко, по маскам файлов).

.htaccessкопировать
<IfModule mod_expires.c>
    ExpiresActive On
    ExpiresByType image/jpeg             "access plus 1 year"
    ExpiresByType image/png              "access plus 1 year"
    ExpiresByType image/webp             "access plus 1 year"
    ExpiresByType image/svg+xml          "access plus 1 year"
    ExpiresByType image/x-icon           "access plus 1 year"
    ExpiresByType font/woff2             "access plus 1 year"
    ExpiresByType text/css               "access plus 1 year"
    ExpiresByType application/javascript "access plus 1 year"
    ExpiresByType text/html              "access plus 0 seconds"
</IfModule>
.htaccessкопировать
<IfModule mod_headers.c>
    <FilesMatch "\.(jpe?g|png|webp|svg|ico|woff2?|css|js)$">
        Header set Cache-Control "public, max-age=31536000, immutable"
    </FilesMatch>
    <FilesMatch "\.(html|htm)$">
        Header set Cache-Control "no-cache, must-revalidate"
    </FilesMatch>
</IfModule>
  • Поменяли style.css — а у посетителя год висит старая версия. Решение: версионируйте имя файла (app.8f3a.css), а не только содержимое; ?v=2 работает, но query-версии хуже кэшируются прокси.
  • immutable ставьте только на файлы, имя которых меняется при изменении содержимого.
  • HTML кэшировать надолго нельзя — иначе пользователь не увидит обновлений (no-cache, must-revalidate или access plus 0 seconds).

Разбор Cache-Control / Expires / ETag / Varyсправочник HTTP-заголовков; собрать чекбоксами — генератор (блок «Кэш статики»).

2. Сжатие текстовых ответов (gzip / Brotli)

HTML / CSS / JS / JSON / SVG сжимаются в 3–5 раз — самый дешёвый выигрыш по объёму трафика и времени загрузки.

.htaccessкопировать
<IfModule mod_deflate.c>
    AddOutputFilterByType DEFLATE text/html text/plain text/xml text/css
    AddOutputFilterByType DEFLATE application/javascript application/x-javascript
    AddOutputFilterByType DEFLATE application/json application/xml
    AddOutputFilterByType DEFLATE application/rss+xml application/atom+xml
    AddOutputFilterByType DEFLATE image/svg+xml application/vnd.ms-fontobject font/ttf font/otf
</IfModule>
.htaccessкопировать
<IfModule mod_brotli.c>
    AddOutputFilterByType BROTLI_COMPRESS text/html text/css application/javascript application/json image/svg+xml
</IfModule>
  • НЕ добавляйте в список image/jpeg, image/png, image/webp, video/*, application/zip, application/pdf — они уже сжаты, выигрыша ноль, а CPU тратится.
  • woff2 тоже уже сжат (в отличие от ttf / otf).
  • mod_gzip — древний модуль Apache 1.3 / 2.0, не путать с mod_deflate; двойное сжатие (gzip поверх Brotli) недопустимо — используйте <IfModule>-обёртки или что-то одно.

Собрать чекбоксами — генератор (блок «GZIP»; там же блок «Отдавать WebP/AVIF» — отдавать современные форматы картинок через .htaccess, если рядом лежат .webp/.avif-версии).

3. ETag, Last-Modified и ответы 304

При повторном запросе браузер шлёт If-None-Match / If-Modified-Since; если файл не изменился, сервер отвечает 304 Not Modified без тела — экономит трафик. Apache по умолчанию ставит и ETag, и Last-Modified.

.htaccessкопировать
# упростить ETag — убрать inode (важно, если сайт на нескольких серверах)
FileETag MTime Size

# или вовсе отключить ETag и полагаться на Last-Modified + Cache-Control:
# FileETag None
  • ETag по умолчанию включает inode файла → на кластере / нескольких бэкендах ETag разных серверов не совпадают, браузер каждый раз перекачивает файл — ставьте FileETag MTime Size или FileETag None.
  • Если статике уже выставлен Cache-Control: max-age=…, immutable, условные запросы для неё всё равно не сработают (браузер не пойдёт на сервер до истечения max-age) — это нормально.

Подробнее про эти заголовки — справочник HTTP-заголовков → кэш.

4. Отдавать заранее сжатые файлы (.gz)

Если на этапе сборки / деплоя рядом с app.js кладёте app.js.gz — Apache может отдавать готовый .gz без сжатия на лету (экономия CPU, заметно для крупных бандлов). Продвинутый приём.

.htaccessкопировать
<IfModule mod_rewrite.c>
    RewriteEngine On
    # клиент поддерживает gzip и рядом есть непустой <файл>.gz — отдаём его
    RewriteCond %{HTTP:Accept-Encoding} \bgzip\b
    RewriteCond %{REQUEST_FILENAME}\.gz -s
    RewriteRule ^(.+)\.(css|js)$ $1.$2.gz [QSA,L]
</IfModule>
<IfModule mod_headers.c>
    <FilesMatch "\.css\.gz$">
        Header set Content-Encoding gzip
        Header set Content-Type "text/css"
    </FilesMatch>
    <FilesMatch "\.js\.gz$">
        Header set Content-Encoding gzip
        Header set Content-Type "application/javascript"
    </FilesMatch>
</IfModule>
Header append Vary Accept-Encoding
  • Обязателен Vary: Accept-Encoding — иначе прокси может отдать gzip-байты клиенту без поддержки gzip.
  • Следите, чтобы mod_deflate не сжимал .gz повторно.
  • Это для статики, которую вы пересобираете при деплое, — не для динамики.

Проверить, что RewriteRule срабатывает на нужном URL, — RewriteRule-тестер.

5. Подсказки браузеру через заголовки (preload)

HTTP-заголовок Link: rel=preload говорит браузеру заранее начать грузить критичные ресурсы (CSS, шрифт), не дожидаясь парсинга HTML; X-DNS-Prefetch-Control разрешает заранее резолвить DNS внешних доменов.

.htaccessкопировать
<IfModule mod_headers.c>
    # заранее начать грузить критичные ресурсы
    Header add Link "</css/app.css>; rel=preload; as=style"
    Header add Link "</fonts/main.woff2>; rel=preload; as=font; type=font/woff2; crossorigin"
    # резолвить DNS внешних доменов заранее
    Header set X-DNS-Prefetch-Control "on"
</IfModule>
  • preload ресурса, который не нужен в первые секунды, наоборот вредит — отнимает полосу у действительно нужного.
  • as=font требует crossorigin, даже если шрифт с того же домена.
  • Не плодите десятки Link rel=preload — это не «загрузить всё сразу».

Разбор заголовка Link и других контент-заголовков — справочник HTTP-заголовков → контент.

6. Что НЕ ускоряет (частые заблуждения)

  • KeepAlive On, KeepAliveTimeout, MaxKeepAliveRequests, EnableSendfile, Timeout, MaxRequestWorkers — это директивы конфига сервера / vhost; в .htaccess они дадут 500 Internal Server Error (… not allowed here) или будут проигнорированы.
  • Добавлять image/jpeg, image/png, image/webp, video/* в mod_deflate — трата CPU без выигрыша (см. раздел 2).
  • Cache-Control: immutable (или большой max-age) на HTML и JSON-API — пользователь застрянет на старой версии (см. раздел 1).
  • ETag с inode за балансировщиком — ломает кэш (см. раздел 3).
  • mod_pagespeed от Google — заброшен с 2018, в современных Apache не собирается; не закладывайтесь.
  • .htaccess читается и парсится Apache на каждый запрос ко всем файлам в этом каталоге и выше — гигантский .htaccess (и тем более по копии в каждом подкаталоге) сам по себе замедляет; по возможности выносите правила в конфиг сервера с AllowOverride None.

Линтер /check/ предупреждает о части директив, которые в .htaccess не работают.

Подробно про мифы о производительности .htaccess (тормозит ли он на самом деле, замеры, как ускорить) — статья «.htaccess: мифы о производительности».

Собрать всё это одним блоком в один клик — генератор «производительность .htaccess».

7. Чеклист: ускорение за 5 минут

  1. Статика (картинки, шрифты, CSS / JS) кэшируется на год? — раздел 1, /headers/
  2. Имена ассетов версионируются (меняются при изменении содержимого)? — раздел 1
  3. gzip (или Brotli) включён для HTML / CSS / JS / JSON / SVG? — раздел 2, /generator/
  4. Картинки и видео НЕ сжимаются повторно (mod_deflate без image/*)? — раздел 2, раздел 6
  5. HTML отдаётся с no-cache, must-revalidate, а не с immutable? — раздел 1
  6. Vary: Accept-Encoding стоит, если используете предсжатые .gz-варианты? — раздел 4
  7. В .htaccess нет KeepAlive и прочих директив «из конфига сервера»? — раздел 6, /check/