Нормализация HTTP‑заголовков часто воспринимается как «косметика», но на практике это влияет на кэширование, безопасность, согласованность логов и предсказуемость прокси‑цепочки. Один и тот же набор полей, представленный разными способами, может породить разные ключи кэша, и наоборот — некорректная нормализация способна испортить сессии из‑за неверного обращения с Set-Cookie. Ниже — практичный ликбез с упором на прокси и кэши, а также настройки Nginx и HAProxy.
Что такое «нормализация заголовков» и зачем она нужна
Нормализация — это приведение набора заголовков к предсказуемому, согласованному виду: единые правила регистра имён, объединение допустимых повторов, канонизация списков, отсечение мусора и недопустимых символов. Цели:
- Сократить кардинальность ключей кэша без потери семантики.
- Снизить риск cache poisoning из‑за различной интерпретации полей разными звеньями цепочки (CDN → reverse proxy → origin).
- Повысить устойчивость логирования и метрик: одинаковые запросы выглядят одинаково.
- Соблюсти требования RFC к допустимым формам представления полей и их повторов.
Ключевая идея: нормализация не должна менять смысл. Разрешены только эквивалентные преобразования с точки зрения RFC и вашей бизнес‑логики.
Краткая модель HTTP‑заголовков
Несколько опорных фактов, важных перед настройкой нормализации:
- Имена заголовков регистронезависимы:
X-Headerиx-header— одно и то же. - Многие заголовки допускают повторение. Для части из них несколько строк эквивалентны одной строке со значениями через запятую. Но не для всех.
- Некоторые поля — одноэлементные по смыслу (
Date,Content-Length) и не должны повторяться. Set-Cookie— особый случай: каждое вхождение — отдельная кука, их нельзя «склеивать».- Исторический folding внутри строки устарел: переносы в значениях использовать нельзя.
Списочные поля (Accept, Accept-Encoding, Accept-Language, Cache-Control, Vary) обычно можно объединять, сведя повторы к одной строке со значениями через запятую. Порядок сохраняйте там, где он влияет на семантику (например, предпочтения в Accept).
Многократные значения и когда их можно объединять
Для списочных полей повторные строки безопасно объединить, чтобы привести запросы/ответы к канонической форме и не плодить эквивалентные варианты.
Типичные безопасные для объединения поля:
Accept,Accept-Encoding,Accept-Language,TECache-Control,Pragma,Warning(аккуратно, еслиWarningиспользуется приложением)Vary(важно для кэша — единообразный порядок и отсутствие дубликатов)
Поля, которые объединять нельзя либо не стоит:
Set-Cookie— строго запрещено склеивать.Date,Content-Length,Host(в запросе),Transfer-Encoding— по смыслу одинарные.Cookieв запросе: если клиент прислал несколько строк, их объединяют через;и пробел, а не через запятую (важно для парсинга на бэкенде).
Перед объединением проверьте специфику поля. Склейка допустима только там, где это прямо разрешено спецификацией. Исключения вроде
Set-Cookieобрабатывайте отдельно.

Почему Set-Cookie — отдельный класс проблемы
Set-Cookie несёт состояние для клиента: каждое вхождение — самостоятельная кука с атрибутами (Path, Domain, Secure, HttpOnly, SameSite). Объединение таких строк через запятую ломает синтаксис и поведение браузера: потеря части кук, непредсказуемые сессии, баги авторизации.
Что ещё важно вокруг Set-Cookie:
- Кэш: многие прокси избегают кэширования ответов с
Set-Cookieили требуют явной политики — иначе приватный ответ может утечь из кэша. - Логи и метрики: не агрегируйте
Set-Cookieв одну строку, чтобы не терять данные и не усложнять парсинг. - Перезапись: добавляя атрибуты (например,
SameSite), не уничтожайте другие вхождения заголовка и не нарушайте порядок.
Для использования атрибута Secure нужен HTTPS. Если вы только настраиваете контур, позаботьтесь об инфраструктуре для TLS заранее — помогут SSL-сертификаты.
Нормализация и кэш: что может пойти не так
Кэш на границе (reverse proxy, CDN, слой внутри приложения) использует подмножество заголовков для формирования ключа и определения кэшируемости. Ошибки нормализации приводят к двум крайностям:
- Раздувание ключей: одно и то же состояние представлено сотней строковых вариантов — промахи и холодный кэш.
- Избыточная агрегация: разные по смыслу состояния сведены к одному ключу — утечки и некорректные ответы.
Частые источники ошибок:
Vary: порядок и дубликаты.Vary: Accept-Encoding, Accept-LanguageиVary: Accept-Language, Accept-Encodingдолжны считаться одинаковыми.Accept-Encoding: клиенты присылают варианты вроде «gzip, deflate, br», «br, gzip», «gzip». Если origin отдаёт только gzip и br, канонизируйте до бинарного признака поддержки.Cookieв запросе иSet-Cookieв ответе: бережно учитывайте их при построении ключа и политике кэша.
Если вы уже оптимизируете контент под клиента, пригодится материал о выборе формата изображений и стабилизации ключа через map: WebP/AVIF в Nginx через map и кэш. Для продвинутого кеширования на уровне шаблонов посмотрите SSI и сабзапросы с кэшем.
Nginx: практики нормализации
Nginx по умолчанию объединяет большинство повторяющихся заголовков через запятую, а Set-Cookie хранит как отдельные вхождения. Но есть нюансы.
Валидация входящих заголовков
ignore_invalid_headers on;— отбрасывать некорректные поля.underscores_in_headers off;— запретить подчёркивания в именах (полезно для совместимости).
Это уменьшает шум и поверхность атак (например, несогласованность X-Header и X_Header).
Кэш и взаимодействие с Cookie/Set‑Cookie
Проектируя ключ кэша, минимизируйте влияние вариативных заголовков при сохранении корректности. Часто применяют:
- Пропуск кэша при наличии маркеров авторизации (
Authorization, определённые куки). - Исключение из кэша ответов, содержащих
Set-Cookie, для приватных маршрутов. - Нормализацию
Accept-Encodingдо конечного множества, поддерживаемого бэкендом.
Пример: пропустить кэш при авторизационных куках и не отдавать из кэша ответы с Set-Cookie на приватном префиксе. Адаптируйте условия под свою авторизацию.
map $http_cookie $has_auth_cookie {
default 0;
~*session= 1;
~*auth= 1;
}
proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=app_cache:100m inactive=10m use_temp_path=off;
server {
listen 443 ssl http2;
location /private/ {
proxy_no_cache $has_auth_cookie;
proxy_cache_bypass $has_auth_cookie;
proxy_pass http://backend;
}
location /public/ {
proxy_cache app_cache;
# Не отдавать из кэша ответ, если бэкенд пытается ставить куку
proxy_no_cache $upstream_http_set_cookie;
proxy_cache_bypass $upstream_http_set_cookie;
proxy_pass http://backend;
}
}
Замечание: переменная $upstream_http_set_cookie в разных версиях и при множестве вхождений ведёт себя по‑разному. Тестируйте в своём окружении; ориентируйтесь на факт наличия заголовка, а не его содержимое.
Канонизация Accept-Encoding
Для стабилизации ключа удобно приводить Accept-Encoding к бинарному признаку возможностей клиента: поддерживает ли br или хотя бы gzip.
map $http_accept_encoding $ae_norm {
default "";
~*\bbr\b br;
~*\bgzip\b gzip;
}
# Включение $ae_norm в ключ кэша
proxy_cache_key "$scheme:$proxy_host$request_uri|ae=$ae_norm";
Vary: порядок и дубликаты
Если приложение формирует Vary, следите за порядком и отсутствием дубликатов. На уровне Nginx можно дополнять Vary нужными полями, не создавая повторов.
add_header Vary Accept-Encoding always;
Если приложение само добавляет Vary, будьте осторожны: дубли в большинстве кэшей не критичны, но повышают кардинальность ключей, если нормализация не выполняется.
HAProxy: управление повторяющимися заголовками и нормализация
HAProxy даёт гибкие действия для заголовков. Полезные приёмы:
http-request set-header/http-response set-header— установить заголовок, заменив все существующие вхождения.http-request add-header/http-response add-header— добавить новое вхождение, не трогая существующие.http-request del-header/http-response del-header— удалить все вхождения.
Списочные поля можно привести к единому виду, заменив все вхождения на одно объединённое. Для получения объединённого значения используйте выборку, возвращающую все вхождения в одну строку для вашей версии HAProxy.
backend app
# Пример: нормализуем Cache-Control ответа, сведя повторы к одной строке
http-response set-header Cache-Control %[res.hdrs(Cache-Control)]
# Пример: запрещаем объединение Set-Cookie — только добавление/удаление
acl is_static path_beg -i /assets/ /static/
http-response del-header Set-Cookie if is_static
Замечания по Set-Cookie в HAProxy:
- Не выполняйте действий, которые могут склеить несколько
Set-Cookieв одно поле. - При добавлении атрибутов (например,
Secure) соблюдайте структуру каждого вхождения.

Частые ловушки и анти‑паттерны
- Слепое объединение всех повторяющихся заголовков. Это ломает
Set-Cookieи может нарушить семантикуCookieиWarning. - Непродуманное добавление
Vary«на каждый чих». Чем больше полей, тем выше кардинальность ключей. - Опора на порядок списков, когда он не гарантируется. Для
Acceptпорядок важен; дляVary— нет. - Логирование сырых заголовков без нормализации. Аналитика зашумляется, сравнивать трафик труднее.
- Отсутствие end‑to‑end теста прохождения дублей через всю цепочку. Разные компоненты ведут себя по‑разному.
Тестирование: как проверить свою гипотезу
Проверяйте нормализацию CLI‑инструментами и логами на каждом хопе.
- Отправьте запрос с дублирующимися заголовками и убедитесь, как он дойдёт до бэкенда.
- Верните ответ с несколькими
Set-Cookieи проверьте, что клиент видит их раздельно, а кэш ведёт себя ожидаемо. - Сверьте логи и дампы на входе и выходе прокси.
# Многократные значения списочного поля
curl -i -H "Accept-Encoding: gzip" -H "Accept-Encoding: br" https://example.test/
# Две куки в ответе: проверьте, что они не склеены
curl -i https://example.test/set-cookies
Если крутите Nginx/HAProxy на собственном окружении, удобнее тестировать на изолированном стенде, например на VDS, а в прод выкатывать поэтапно с контролем метрик.
Производительность и хранение: почему нормализация окупается
Стабильный ключ кэша и канонизированные заголовки сокращают число уникальных вариантов, повышают hit‑ratio и снижают нагрузку на origin. Это особенно заметно при разнообразии клиентов (мобильные браузеры, регионы, агенты без br, API‑клиенты). Простая канонизация Accept-Encoding и аккуратный Vary часто дают двузначный рост hit‑ratio без изменений в приложении.
Нормализация также упрощает кросс‑сервисную отладку: когда все звенья цепочки видят одно и то же представление, легче ловить edge‑кейсы и реплицировать проблемы.
Мини‑гайд по проектированию политики нормализации
- Определите сценарии кэширования: что кэшируем, что нет; какие заголовки участвуют в ключе.
- Разделите поля на объединяемые (списки) и необъединяемые (
Set-Cookie, одноэлементные). - Канонизируйте шумные списки (
Accept-Encoding,Accept-Language, при необходимостиCache-Control), не меняя смысла. - Определите политику для
Cookie/Set-Cookie: где пропуск кэша, где удаление или переписывание. - Сделайте end‑to‑end тесты: генерация запросов, проверка логов на всех слоях, сверка поведения клиента.
- Настройте алерты на аномалии
Varyи рост кардинальности ключей.
Итоги
Нормализация HTTP‑заголовков — дисциплина на стыке стандартов, кэширования и безопасности. Понимая, какие поля можно объединять, а какие требуют особого обращения (в первую очередь Set-Cookie), вы повышаете предсказуемость цепочки прокси, улучшаете hit‑ratio и избегаете трудноотлавливаемых багов с сессиями. Начните с малого: включите базовую валидацию заголовков, канонизируйте критичные списки, аккуратно определите политику для Cookie/Set‑Cookie — и измеряйте эффект.


