A/B-тесты в Nginx: ошибки, кеш и сохранение групп
Распределить пользователей между двумя версиями через Nginx можно без изменений в приложении, но тест легко испортить кешем, сменой группы при каждом запросе или неверным учётом ботов.

Nginx может разделить трафик между двумя версиями страницы, но сам по себе не делает A/B-тест достоверным. Частая ошибка выглядит безобидно: 50% запросов получают вариант A, остальные — B, а затем кеш отдаёт страницу одной группы другой или пользователь перескакивает между версиями. Ниже — практическая схема для Nginx 1.25.x: распределение, сохранение группы в cookie, настройка кеша, логирование и проверки перед запуском.
Что именно должен делать Nginx
На уровне reverse proxy у теста есть четыре отдельные задачи:
- назначить посетителю вариант;
- сохранить назначение между запросами;
- не смешать ответы вариантов в прокси-кеше;
- передать группу в приложение и записать её в лог.
Модуль split_clients решает только первую задачу. Он делит значение на группы по хешу, а не понимает, что такое конверсия, сессия или статистическая значимость.
Распределение 50/50 — это свойство маршрутизации, а не доказательство того, что эксперимент проведён корректно.
[1] Sticky assignment — закрепление пользователя за одним вариантом на время теста. Без него один и тот же посетитель может увидеть разные версии, а сравнение результатов потеряет смысл.
Три подхода к распределению трафика
| Подход | Как работает | Цена решения | Когда выбирать |
|---|---|---|---|
| Хеш от IP | Группа вычисляется из $remote_addr | Просто, но мобильная сеть и прокси могут менять адрес | Для быстрых технических проверок без персонализации |
| Cookie | Nginx назначает вариант и сохраняет его в браузере | Нужно учитывать первый ответ, кеш и очистку cookie | Для пользовательского теста с постоянной группой |
| Назначение в приложении | Backend хранит группу в сессии или базе | Больше изменений в коде и инфраструктуре | Когда важны авторизация, единый профиль и несколько сервисов |
Для простого теста лендинга обычно достаточно cookie на границе Nginx. Для авторизованного продукта надёжнее назначать группу в приложении: один пользователь может работать с нескольких устройств, а cookie не является источником истины.
Рабочая схема с cookie
Пример ниже рассчитан на Nginx 1.25.x. Он использует только стандартные директивы модулей HTTP. Значения cookie принимаются лишь для двух известных вариантов; любое другое значение получает новое назначение.
http {
map $cookie_ab_variant $ab_variant_from_cookie {
control control;
variant variant;
default "";
}
# Для запроса без корректной cookie создаём новое назначение.
split_clients $request_id $ab_new_variant {
50% control;
* variant;
}
map $ab_variant_from_cookie $ab_variant {
control control;
variant variant;
default $ab_new_variant;
}
log_format ab '$remote_addr [$time_local] "$request" '
'status=$status variant=$ab_variant request_id=$request_id';
access_log /var/log/nginx/access.log ab;
server {
listen 443 ssl;
server_name example.com;
location / {
proxy_set_header X-AB-Variant $ab_variant;
proxy_set_header X-Request-ID $request_id;
add_header Set-Cookie "ab_variant=$ab_variant; Path=/; Max-Age=2592000; SameSite=Lax" always;
proxy_cache website;
proxy_cache_key "$scheme$request_method$host$request_uri|$ab_variant";
proxy_pass http://application;
}
}
}
Схема делает следующее: если cookie содержит control или variant, Nginx использует это значение. При первом запросе берётся новый результат split_clients. Затем группа передаётся backend в заголовке X-AB-Variant, а значение варианта добавляется в ключ кеша.
Почему нельзя делить только по IP
Внутри одной корпоративной сети тысячи пользователей могут иметь один внешний IP и попасть в одну группу. У мобильного пользователя адрес способен измениться между запросами. IP также плохо подходит для устройств за CDN, балансировщиком или NAT: Nginx может видеть адрес промежуточного узла, а не клиента.
Если всё же используется IP-хеш, сначала проверьте цепочку прокси и настройку доверенных адресов. Не подставляйте заголовок X-Forwarded-For в эксперимент без контроля: клиент может отправить его сам, если предыдущий прокси не перезаписывает заголовок.
Типичные ошибки, которые портят тест
Группа вычисляется заново на каждом запросе
Выражение на основе $request_id подходит для первоначального назначения, но не для постоянной маршрутизации: идентификатор новый у каждого запроса. Если использовать его напрямую в split_clients, один человек увидит разные варианты. Для закрепления нужна cookie или серверная сессия.
Вариант не входит в ключ кеша
Ключ по умолчанию может учитывать URI, но не вашу переменную группы. Первый закешированный ответ тогда начнёт обслуживать всех посетителей независимо от варианта. В ключ нужно явно добавить $ab_variant, как в примере выше.
Не добавляйте в ключ полный заголовок Cookie: это резко увеличит количество объектов в кеше. Выделите одно нормализованное значение и используйте только его.
Кеш сохраняет персонализированный ответ
Если backend возвращает Set-Cookie, приватные данные или разные ответы по авторизации, публичный кеш может стать неправильным даже при корректном разделении групп. Для авторизованных страниц обычно нужен отдельный маршрут без общего кеша. В спорном случае сначала отключите кеш на тестовом URL и проверьте логику, а затем включайте его с явным ключом и правилами.
Cookie назначается, но её перезаписывает каждый ответ
Директива add_header ... always отправляет заголовок на каждом подходящем ответе. Это не меняет значение при корректной cookie в приведённой схеме, но создаёт лишние заголовки и усложняет диагностику. Проверьте в браузере, что приходит одно значение ab_variant, а промежуточный CDN не добавляет собственную cookie с таким же именем.
В аналитику попадает запрос, а не пользователь
Nginx логирует каждый запрос: HTML, CSS, изображения и API. Если считать все строки как пользователей, вариант с более тяжёлой страницей может выглядеть «победителем» только из-за большего числа ресурсов. Для бизнес-метрик передавайте группу в приложение или систему аналитики и считайте уникальные сессии по её правилам.
Как проверять конфигурацию до запуска
- Проверьте синтаксис. Выполните
nginx -tс тем же конфигурационным файлом, который использует сервис. Ошибка должна отсутствовать до reload. - Проверьте первый запрос. Отправьте запрос без cookie и убедитесь, что в ответе есть только ожидаемое значение
ab_variant. - Проверьте повторный запрос. С тем же cookie запрос должен получать тот же
X-AB-Variant, а в access log должна появляться соответствующая группа. - Проверьте кеш. Очистите старые объекты, запросите одну страницу из каждой группы и сравните заголовки кеша и тело ответа.
- Проверьте обходные пути. Отдельно протестируйте редиректы, ошибки, API, авторизацию, CDN и запросы с отключёнными cookie.
Минимальные команды для проверки заголовков
curl -I https://example.com/test
curl -I --cookie "ab_variant=control" https://example.com/test
curl -I --cookie "ab_variant=variant" https://example.com/test
Не сравнивайте только статус 200. Важны Set-Cookie, заголовок группы, Age или служебный заголовок кеша, а также фактическое содержимое страницы.
Где схема ломается
Cookie не гарантирует единую группу для пользователя, который очистил данные браузера, открыл приватное окно или перешёл с другого устройства. Она также не подходит как единственный идентификатор для авторизованного клиента. В таком сценарии Nginx может передавать предварительное назначение, но итоговую группу должен хранить backend.
Отдельный риск — несколько доменов и поддоменов. Cookie с атрибутом Path=/ действует в пределах текущего домена, но не автоматически во всей доменной зоне. Если эксперимент проходит на нескольких хостах, заранее определите домен cookie и убедитесь, что требования безопасности это допускают.
Боты, мониторинг и внутренние сотрудники тоже попадут в выборку, если не исключить их на уровне аналитики или маршрутизации. Не исключайте трафик по одному User-Agent без проверки: такие значения легко меняются и подделываются.
Что сделать дальше
Перед включением трафика зафиксируйте срок теста, основную метрику и правило остановки. Затем соберите отдельно данные по вариантам, статусам ответа, кеш-хитам, устройствам и авторизованным пользователям. После изменения конфигурации следите не только за конверсией, но и за долей ответов 4xx/5xx, ростом кеша и количеством запросов без корректной группы.
Инвалидация кеша и расчёт статистической значимости — отдельные темы. В этой схеме главное сначала обеспечить постоянство группы и изоляцию ответов, а уже потом сравнивать поведение пользователей.
Тест и проверочная: Быстрые ответы с объяснением, почему остальные варианты неверны
Спросить за 10 ₽Источники: Nginx ngx_http_split_clients_module, Nginx ngx_http_proxy_module: proxy_cache_key
Частые вопросы
Можно ли провести A/B-тест в Nginx без изменений в приложении?
Почему одного split_clients недостаточно?
Нужно ли добавлять A/B-группу в proxy_cache_key?
Что произойдёт, если пользователь удалит cookie?
Можно ли делить трафик по IP-адресу?
Как понять, что тест сломан из-за кеша?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.