HTTP-заголовки в .htaccess
Справочник HTTP-заголовков, которые удобно выставлять через .htaccess модулем mod_headers (директива Header, для заголовков запроса — RequestHeader): что делает каждый, как написать, грабли.
Собрать набор заголовков безопасности чекбоксами — генератор (блок «Security headers»); готовый блок — рецепт «Заголовки безопасности»; общая справка по Header/RequestHeader как директивам — справочник директив. Важно: заголовки, которые сервер/PHP/CMS уже выставляет, лучше менять с Header always set … (перезапишет, в т. ч. для ответов об ошибках), либо не дублировать; и оборачивать блок в <IfModule mod_headers.c>…</IfModule>, чтобы .htaccess не падал на хостинге без модуля.
Как ставить заголовки в .htaccess
Посмотреть, какие заголовки реально отдаёт ваш сайт — чекер сервера.
mod_headers даёт две директивы — Header (заголовок ответа) и RequestHeader (заголовок запроса, увидит бэкенд/прокси).
Header— добавить/изменить/удалить заголовок ответа. Действия:set(установить, заменив — для ответа об ошибке может не сработать),always set(то же, но для всех ответов, включая ошибки и редиректы — для заголовков безопасности почти всегда нужноalways),append(добавить значение через запятую),add(добавить ещё одну строку заголовка — для multi-value вродеSet-Cookie/Link),unset(удалить),edit ИМЯ <regex> <замена>/edit*(regex-замена в значении),merge(какappend, но без дублей). Опц. условиеenv=ИМЯ/env=!ИМЯ. Синтаксис:Header [always] <действие> <Имя> ["значение"] [env=…]. Пример:Header always set X-Frame-Options "SAMEORIGIN". (Грабли: если PHP/CMS сама шлёт заголовок —Header always set …перезапишет; для «жёсткой» замены — сначалаHeader unset Имя, потомHeader set Имя …. Заголовки можно ставить адресно — внутри<FilesMatch>/<If>.)RequestHeader— то же для заголовка запроса (который увидит бэкенд / прокси / CGI, не клиент). Синтаксис:RequestHeader <действие> <Имя> ["значение"]. Пример:RequestHeader set X-Forwarded-Proto "https".<IfModule mod_headers.c>— оборачивайте блок сHeader-директивами, чтобы.htaccessне вернул 500 на сервере безmod_headers. Синтаксис:<IfModule mod_headers.c> Header always set X-Content-Type-Options "nosniff" </IfModule>. Пример: то же.
Заголовки безопасности
Базовая «оборона» сайта на уровне ответа — кликджекинг, MIME-sniffing, утечка реферера, разрешения фич, изоляция. Все — с Header always set …. Собрать нужные чекбоксами — генератор (блок «Security headers»), готовый блок — рецепт.
Strict-Transport-Security(HSTS) — браузер запоминает, что сайт надо открывать только по HTTPS, в течениеmax-ageсекунд. Синтаксис:Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains". Пример: то же; с; preload— заявка на встроенный в браузеры preload-список. (Грабли: после включения сайт нельзя открыть поhttp://в течениеmax-age, даже если TLS сломается;includeSubDomainsраспространяет это на все поддомены;preloadпрактически необратим (заявка на удаление в hstspreload.org); включать ТОЛЬКО когда сайт навсегда на HTTPS, начинать с маленькогоmax-age, например 300.)Content-Security-Policy(CSP) — белый список источников для скриптов, стилей, картинок, фреймов, XHR — главная защита от XSS и встраивания. Синтаксис:Header always set Content-Security-Policy "default-src 'self'; img-src 'self' data:; …". Пример:Header always set Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; frame-ancestors 'self'". (Грабли: легко сломать inline-<script>/<style>, внешние CDN, аналитику; начинайте сContent-Security-Policy-Report-Only+report-uri/report-to, смотрите нарушения, потом включайте боевой;'unsafe-inline'/'unsafe-eval'ослабляют защиту;frame-ancestorsзаменяетX-Frame-Options.)Content-Security-Policy-Report-Only— то же, что CSP, но ничего не блокирует — только шлёт отчёты о том, что нарушило бы политику. Синтаксис:Header always set Content-Security-Policy-Report-Only "default-src 'self'; report-uri /csp-report". Пример: то же. (Используйте, чтобы обкатать политику без поломок на проде.)X-Frame-Options— можно ли встраивать сайт в<iframe>:DENY(никому) /SAMEORIGIN(только со своего домена). Защита от кликджекинга. Синтаксис:Header always set X-Frame-Options "SAMEORIGIN". Пример: то же. (Для современных браузеров покрывается CSPframe-ancestors, ноX-Frame-Optionsещё нужен для совместимости;ALLOW-FROMбольшинством браузеров не поддерживается — используйте CSP.)X-Content-Type-Options—nosniff— запретить браузеру «угадывать» MIME-тип по содержимому. Синтаксис:Header always set X-Content-Type-Options "nosniff". Пример: то же.Referrer-Policy— сколько информации из адреса страницы утекает вReferer:no-referrer,same-origin,strict-origin-when-cross-origin(рекомендуемый дефолт),origin,unsafe-url(не надо). Синтаксис:Header always set Referrer-Policy "strict-origin-when-cross-origin". Пример: то же.Permissions-Policy(бывш.Feature-Policy) — какие браузерные фичи разрешены и кому:geolocation=(),camera=(self),microphone=(),fullscreen=(self),payment=(),interest-cohort=(). Синтаксис:Header always set Permissions-Policy "geolocation=(), microphone=(), camera=()". Пример: то же. (У старогоFeature-Policyдругой синтаксис значений — ставьте новое имя.)Cross-Origin-Opener-Policy(COOP) — изолировать окно браузера от окон других origin:same-origin/same-origin-allow-popups/unsafe-none. Синтаксис:Header always set Cross-Origin-Opener-Policy "same-origin". Пример: то же. (same-originможет сломать межоконное взаимодействие с виджетами/OAuth-попапами; для попапов —same-origin-allow-popups.)Cross-Origin-Embedder-Policy(COEP) — требовать, чтобы все встроенные кросс-origin-ресурсы явно разрешали встраивание:require-corp/credentialless/unsafe-none. Синтаксис:Header always set Cross-Origin-Embedder-Policy "require-corp". Пример: то же. (Грабли:require-corpломает встраивание любого стороннего ресурса безCross-Origin-Resource-Policy/Access-Control-*— включать осознанно.)Cross-Origin-Resource-Policy(CORP) — кто может встраивать ваш ресурс:same-origin/same-site/cross-origin. Синтаксис:Header set Cross-Origin-Resource-Policy "same-origin". Пример: то же. (Для публичных картинок/шрифтов, которые должны грузиться откуда угодно, —cross-origin.)X-XSS-Protection— рекомендуемое значение0(отключить старую встроенную фильтрацию XSS — она убрана/небезопасна; защита — CSP). Синтаксис:Header always set X-XSS-Protection "0". Пример: то же. (Значения1/1; mode=blockустарели и могли создавать уязвимости — ставьте0или не ставьте вовсе.)X-Permitted-Cross-Domain-Policies—none— запретить Adobe Flash/PDF-плагинам кросс-доменный доступ черезcrossdomain.xml. Синтаксис:Header always set X-Permitted-Cross-Domain-Policies "none". Пример: то же. (Сейчас почти неактуально — Flash мёртв; ставят «для полноты» в security-аудитах.)
Заголовки безопасности — лишь часть картины: см. также гид «Безопасность сайта через .htaccess» — защита файлов, доступ по IP, пароль, запрет выполнения PHP в загрузках, анти-бот.
CORS (доступ с других доменов)
Когда JS на странице a.com должен обратиться к ресурсу на b.com — браузер пускает это только если b.com прислал нужные Access-Control-*-заголовки.
Access-Control-Allow-Origin— какому домену (origin) разрешён cross-origin-доступ:*(всем) или конкретныйhttps://app.example.com. Синтаксис:Header set Access-Control-Allow-Origin "*". Пример: то же /Header set Access-Control-Allow-Origin "https://app.example.com". (Грабли:*несовместим сAccess-Control-Allow-Credentials: true(браузер отбросит); несколько доменов через запятую нельзя — нужноSetEnvIf Origin "^https://(a|b)\.example\.com$" CORS_ORIGIN=$0+Header set Access-Control-Allow-Origin "%{CORS_ORIGIN}e" env=CORS_ORIGIN+Header append Vary Origin.)Access-Control-Allow-Methods— какие HTTP-методы разрешены в cross-origin-запросе (отдаётся в ответ на preflightOPTIONS). Синтаксис:Header set Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS". Пример: то же.Access-Control-Allow-Headers— какие нестандартные заголовки запроса разрешены. Синтаксис:Header set Access-Control-Allow-Headers "Content-Type, Authorization, X-Requested-With". Пример: то же.Access-Control-Allow-Credentials—true— разрешить отправку кук/HTTP-авторизации в cross-origin-запросах (тогдаAllow-Originобязан быть конкретным, не*). Синтаксис:Header set Access-Control-Allow-Credentials "true". Пример: то же.Access-Control-Max-Age— сколько секунд браузер может кэшировать ответ на preflight, чтобы не слатьOPTIONSперед каждым запросом. Синтаксис:Header set Access-Control-Max-Age "86400". Пример: то же.Access-Control-Expose-Headers— какие заголовки ответа JS-код вправе прочитать (по умолчанию доступен ограниченный набор). Синтаксис:Header set Access-Control-Expose-Headers "Content-Length, X-Total-Count". Пример: то же.Vary: Origin— при динамическомAccess-Control-Allow-Origin(зависящем отOriginзапроса) обязательно добавляйтеHeader append Vary Origin— иначе прокси/CDN отдаст закэшированныйAllow-Originдля другого домена. Синтаксис:Header append Vary Origin. Пример: то же.
Preflight-запрос OPTIONS Apache обычно отдаёт как обычный (404/405 + тело). Если нужен «голый» preflight-ответ — пример: RewriteEngine On + RewriteCond %{REQUEST_METHOD} OPTIONS + RewriteRule ^ - [R=200,L] (вернёт 200 без тела; сами CORS-заголовки добавит блок Header set Access-Control-* …). Подробнее про правила — тестер RewriteRule, рецепты.
Кэширование
Заголовки, которыми вы говорите браузеру/прокси/CDN, сколько хранить ответ и когда перепроверять. Часто проще задать через mod_expires (ExpiresByType) — см. директивы mod_expires; ниже — сами заголовки и их смысл.
Cache-Control— главный заголовок кэша:max-age=N(сколько секунд ответ свеж),public/private(можно ли кэшировать в общих прокси/CDN или только в браузере),no-cache(кэшировать можно, но перед использованием ревалидировать на сервере),no-store(вообще не кэшировать — для приватных данных),immutable(в течениеmax-ageне ревалидировать даже при перезагрузке),s-maxage=N(max-ageтолько для общих кэшей/CDN),must-revalidate. Синтаксис:Header set Cache-Control "max-age=31536000, public, immutable". Пример (статика, внутри<FilesMatch "\.(css|js|woff2|jpe?g|png|webp|svg|ico)$">): то же; (HTML):Header set Cache-Control "no-cache". (Грабли: статику кэшируйте надолго ТОЛЬКО при версионировании имён (style.v2.css) — иначе пользователи получат старую; HTML —no-cacheили короткийmax-age; не путайтеno-cache(ревалидировать) иno-store(не хранить вообще).)Expires— абсолютная дата/время, до которой ответ свеж (Expires: Thu, 31 Dec 2026 23:59:59 GMT) — старый аналогCache-Control: max-age. Если есть оба —Cache-Controlглавнее. Синтаксис:Header set Expires "Wed, 01 Jan 2025 00:00:00 GMT". Пример: то же. (Руками задавать абсолютную дату неудобно — обычно прощеmod_expires:ExpiresActive On+ExpiresByType … "access plus 1 year"— это автоматически считает иExpires, иCache-Control: max-age.)ETag— «отпечаток» содержимого ответа; браузер при повторном запросе шлётIf-None-Match: <etag>, сервер отвечает304 Not Modified(без тела), если не изменилось. Apache генерирует сам (см.FileETagв справочнике директив). Синтаксис:Header unset ETag(часто отключают). Пример:Header unset ETag+FileETag None. (Грабли: дефолтный ETag в Apache включает inode файла → на кластере из нескольких серверов ETag-и разъезжаются и кэш не срабатывает; либоFileETag MTime Size(без inode), либо отключить и полагаться наCache-Control/Last-Modified.)Last-Modified— дата последнего изменения файла; браузер шлётIf-Modified-Since, сервер отвечает304, если файл не менялся. Apache ставит сам для статических файлов. Синтаксис:Header unset Last-Modified(если зачем-то нужно убрать). Пример: обычно ничего не делают — Apache сам.Vary— от каких заголовков запроса зависит ответ — чтобы кэши хранили разные варианты:Vary: Accept-Encoding(есть сжатая и несжатая версии),Vary: Origin(CORS),Vary: Accept(контент-негоциация). Синтаксис:Header append Vary Accept-Encoding. Пример: то же. (Грабли:Vary: User-AgentилиVary: Cookieпочти полностью убивает эффективность общего кэша — добавляет вариант на каждый UA/куку; используйте только при реальной необходимости.)Pragma—no-cache— реликт HTTP/1.0; современные браузеры ориентируются наCache-Control. Иногда ставят «на всякий» вместе сCache-Control: no-cache. Синтаксис:Header set Pragma "no-cache". Пример: то же. (ТолькоPragmaбезCache-Controlненадёжно; современным клиентам не нужен.)
Подробнее про директивы кэширования — рецепты, mod_expires.
Практическое применение этих заголовков (что кэшировать на год, что — на ноль секунд, как не застрять на старой версии) — в гиде «Ускорение сайта через .htaccess».
Контент, индексация, разное
Заголовки про индексацию поисковиками, скачивание файлов, ресурс-хинты и кодирование.
X-Robots-Tag— управление индексацией заголовком (а не мета-тегом) — полезно для не-HTML: PDF, картинки, файлы, ответы API. Значения:noindex,nofollow,none(=noindex, nofollow),noarchive,nosnippet; адресно для бота:googlebot: noindex. Синтаксис:Header set X-Robots-Tag "noindex, nofollow". Пример (внутри<FilesMatch "\.(pdf|docx?)$">):Header set X-Robots-Tag "noindex".Content-Disposition— как браузер обращается с ответом:attachment; filename="report.pdf"— скачать как файл (с предложенным именем);inline— показать в окне браузера. Синтаксис:Header set Content-Disposition "attachment; filename=\"file.zip\"". Пример (внутри<FilesMatch "\.(zip|tar\.gz)$">):Header set Content-Disposition attachment. (Для нелатинских имён файлов нужна формаfilename*=UTF-8''…— это лучше делать в коде приложения, не в.htaccess.)Content-Type— MIME-тип тела ответа (text/html; charset=UTF-8). Обычно задаётся неHeader-ом, аAddType(по расширению) илиForceType(всем файлам в каталоге) изmod_mime— см. директивы mod_mime.Header set Content-Type …тоже работает (внутри<Files>), ноAddTypeчище. Синтаксис:AddType application/manifest+json .webmanifest(предпочтительно). Пример: то же.Link—rel-связи и ресурс-хинты заголовком (а не<link>в HTML):</style.css>; rel=preload; as=style,<https://fonts.gstatic.com>; rel=preconnect,<https://example.com/page>; rel=canonical. Синтаксис:Header add Link "</css/main.css>; rel=preload; as=style". Пример: то же. (ИспользуйтеHeader add(неset), еслиLink-заголовков несколько.)Content-Encoding— каким кодированием сжато тело (gzip,br). Обычно его выставляетmod_deflate/mod_brotliсам при сжатии — руками не нужно. Исключение: на диске лежит уже сжатый файл (app.js.gz), который вы отдаёте как.js— тогдаHeader set Content-Encoding gzip(чище —AddEncoding gzip .gz, см. фильтры/кодировки). Синтаксис:Header set Content-Encoding gzip(внутри<FilesMatch "\.gz$">). Пример: то же. (Грабли: руками поставленныйContent-Encodingбез реального сжатия = битый, нечитаемый ответ.)Accept-Ranges— поддержка частичных запросов (докачка, перемотка видео):bytes(поддерживается) /none(нет). Apache для статики ставитbytesсам. Синтаксис:Header set Accept-Ranges none(отключить — редко нужно). Пример: то же.X-DNS-Prefetch-Control—on/off— разрешить браузеру заранее резолвить DNS доменов из ссылок на странице. Синтаксис:Header set X-DNS-Prefetch-Control "on". Пример: то же.Clear-Site-Data— попросить браузер очистить данные сайта:"cache","cookies","storage","*". Ставят обычно на конкретный URL (например/logout). Синтаксис:Header set Clear-Site-Data "\"cookies\", \"storage\"". Пример (внутри<Files "logout.php">): то же. (Значения — JSON-строки в двойных кавычках внутри значения заголовка.)
Заголовки запроса (RequestHeader)
RequestHeader меняет то, что увидит бэкенд/прокси/CGI, а не клиент. В значениях Header/RequestHeader доступны %{VARNAME}e (переменная окружения), %{VARNAME}s (SSL-переменная при mod_ssl) и спецтокены (%t, %D); синтаксиса %{HTTP:Имя} тут нет (это mod_rewrite/mod_setenvif/expr) — чтобы прокинуть входящий заголовок, его сначала кладут в env-переменную (SetEnvIf …), потом читают как %{…}e.
X-Forwarded-Proto— сказать бэкенду/приложению, по какому протоколу пришёл запрос на фронт (https/http) — нужно за reverse-proxy/балансировщиком, который терминирует TLS, чтобы приложение генерировалоhttps://-ссылки. Синтаксис:RequestHeader set X-Forwarded-Proto "https". Пример: то же (если этот Apache сам стоит за прокси и снаружи HTTPS).X-Forwarded-For— передать/добавить IP клиента вниз по цепочке проксей. Обычно это делает прокси сам, но иногда нужно явно. Синтаксис:RequestHeader set X-Forwarded-For "%{REMOTE_ADDR}e". Пример: то же. (ДоверяйтеX-Forwarded-Forтолько от своих проксей — клиент может его подделать.)Authorization— иногда заголовокAuthorizationне доходит до PHP-FPM/CGI (теряется по дороге) — частая боль REST API за FPM. Решения:CGIPassAuth On(Apache 2.4.13+, проще всего — пробрасываетAuthorizationв окружение скрипта) либоSetEnvIf Authorization "(.+)" HTTP_AUTHORIZATION=$1(тогда PHP читает$_SERVER['HTTP_AUTHORIZATION']; при желании добавитьRequestHeader set Authorization "%{HTTP_AUTHORIZATION}e"). Синтаксис:CGIPassAuth OnилиSetEnvIf Authorization "(.+)" HTTP_AUTHORIZATION=$1. Пример: то же.
Инструменты и связанное
Генератор .htaccess (блок «Security headers») ·
Рецепты .htaccess (рецепт «Заголовки безопасности») ·
Справочник директив (Header / RequestHeader, mod_expires, mod_mime) ·
Проверка .htaccess (линтер) ·
Тестер RewriteRule ·
Как работает mod_rewrite ·
Блокировка по referer ·
Фильтрация по IP