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

A/B-тест через Nginx можно собрать без изменений в приложении: веб-сервер назначит посетителю вариант, отправит запрос в нужный upstream и сохранит выбор в cookie. Но схема с одним split_clients часто даёт неверные результаты: пользователь прыгает между версиями, кеш смешивает ответы, а аналитика считает один визит двумя разными группами. Ниже — рабочая конфигурация для Nginx 1.25.x и новее, критерии выбора подхода и список проверок перед запуском.
Что должно получиться
В примере есть два backend-сервиса: app_a и app_b. Nginx распределяет пользователей в пропорции 50/50, записывает вариант в cookie ab_test и передаёт его приложению через заголовок X-AB-Variant. Повторные запросы того же пользователя идут в тот же вариант.
Конфигурация рассчитана на Nginx 1.25.x. Директива split_clients относится к модулю ngx_http_split_clients_module; перед установкой проверьте, что он собран в вашей версии командой nginx -V. Имена upstream, домены и порты ниже — примеры, их нужно заменить на ваши.
Главная задача A/B-теста — не разделить запросы пополам, а сохранить неизменным вариант для одного пользователя и измерить результат в одинаковых условиях.
Sticky-сегмент[1] — это закрепление пользователя за одной группой теста. Без него один человек может открыть страницу A, перейти по ссылке и получить страницу B.
Три способа назначить вариант
| Подход | Как работает | Цена и риск |
|---|---|---|
| Хеш IP и User-Agent | split_clients вычисляет вариант из признаков запроса | Не нужен cookie, но мобильные сети и смена User-Agent могут переместить пользователя |
| Cookie поверх хеша | Первый выбор берётся из хеша, затем сохраняется в cookie | Стабильнее для теста, но cookie можно удалить или изменить |
| Назначение в приложении | Приложение выбирает группу и хранит её в своей сессии | Точнее для авторизованных пользователей, но требует изменений в коде и единого хранилища |
Для лендинга и анонимного трафика подойдёт cookie поверх хеша. Для личного кабинета лучше назначать вариант по постоянному идентификатору пользователя в приложении: IP не является идентификатором человека, а за одним NAT могут находиться сотни посетителей.
Базовая конфигурация с закреплением варианта
Сначала объявите upstream-группы и переменные в контексте http. Важно: директивы split_clients и map нельзя бездумно переносить внутрь server, если ваша конфигурация уже использует их на другом уровне. Проверьте допустимый контекст в документации вашей версии.
http {
upstream app_a {
server 127.0.0.1:8001;
}
upstream app_b {
server 127.0.0.1:8002;
}
# Базовый вариант для клиента без корректной cookie.
split_clients "$remote_addr$http_user_agent" $ab_bucket {
50% a;
* b;
}
# Принимаем только ожидаемые значения из cookie.
map $cookie_ab_test $ab_variant {
a a;
b b;
default $ab_bucket;
}
# Cookie отправляется только при первом назначении.
map $cookie_ab_test $ab_set_cookie {
"" "ab_test=$ab_bucket; Path=/; Max-Age=2592000; SameSite=Lax";
default "";
}
map $ab_variant $ab_backend {
a app_a;
b app_b;
}
server {
listen 80;
server_name example.test;
add_header Set-Cookie $ab_set_cookie always;
add_header X-AB-Variant $ab_variant always;
location / {
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-AB-Variant $ab_variant;
proxy_pass http://$ab_backend;
}
}
}
Здесь значение cookie проверяется через map. Если клиент прислал ab_test=c или повреждённое значение, Nginx игнорирует его и использует рассчитанный bucket. Это не защита от намеренного подбора варианта, но она убирает часть случайных ошибок и мусора в аналитике.
Как проверить конфигурацию
- Проверьте синтаксис: выполните
nginx -t. В ответе должны быть строки о корректном синтаксисе и успешной проверке. - Перечитайте конфигурацию командой
nginx -s reload. Старые соединения не должны обрываться только из-за reload. - Откройте сайт в приватном окне. В ответе должен появиться заголовок
Set-Cookie: ab_test=aилиab_test=b. - Повторите запрос с той же cookie. Значение
X-AB-Variantи backend должны остаться прежними. - Удалите cookie и повторите тест несколько раз. Вы получите разные варианты, но на небольшой серии запросов пропорция не обязана быть ровно 50/50.
Почему одного split_clients недостаточно
split_clients делает детерминированное распределение по хешу, а не создаёт запись о пользователе. В примере таким ключом служит комбинация IP и User-Agent. При переходе с Wi-Fi на мобильную сеть IP меняется. При обновлении браузера или использовании встроенного браузера User-Agent тоже может отличаться. Cookie сглаживает эти переходы после первого ответа.
Для теста с авторизацией передавайте вариант в приложение и связывайте его с внутренним идентификатором пользователя. Не подставляйте email, телефон или другой персональный идентификатор в строку хеша без отдельной оценки требований к защите данных.
Кеш: место, где ломается большинство тестов
Если перед Nginx или за ним работает кеш, первый сохранённый ответ может достаться пользователям обеих групп. Например, запрос пользователя A попал в кеш, а следующий посетитель из группы B получил ту же HTML-страницу.
Есть два безопасных варианта:
- разделить ключ кеша по варианту, добавив
$ab_variantв используемый ключ; - отключить кеширование тестируемой страницы на время эксперимента.
Точный синтаксис зависит от того, где находится кеш. Для встроенного proxy cache проверьте, как в вашей конфигурации задан proxy_cache_key. Если используется CDN, его кеш нужно настроить отдельно: cookie может не входить в ключ по умолчанию.
Что передавать в аналитику
Вариант должен попасть в логи и систему аналитики одним и тем же способом. Заголовок X-AB-Variant можно записывать в access log через переменную $ab_variant. Приложение также может отправлять событие с тем же значением. Не назначайте группу заново на стороне JavaScript: это создаёт расхождение между тем, что отдал сервер, и тем, что увидела аналитика.
Проверяйте не только конверсию, но и технические показатели: долю ответов 5xx, время ответа, ошибки загрузки ресурсов и процент пользователей без установленной cookie. Если вариант B чаще получает ошибки, сравнивать его бизнес-результат с A нельзя до устранения технической причины.
Что ломается и как это диагностировать
Оба варианта получают один и тот же ответ
Чаще всего виноват кеш. Проверьте заголовки кеширования и ключ, по которому сохраняется ответ. Если запросы проходят через CDN, временно отключите его кеш для тестового URL или добавьте вариант в правило кеширования.
Cookie не появляется
Посмотрите ответ именно от Nginx, а не только запрос браузера. Заголовок может потеряться на промежуточном прокси. Также проверьте, что переменная $ab_set_cookie не пустая при первом запросе и что директива использует параметр always.
Пользователь меняет вариант
Проверьте, не удаляет ли cookie JavaScript, не задан ли конфликтующий cookie с тем же именем и доменом, а также не проходит ли часть трафика через другой Nginx. Если cookie отключены, стабильность будет зависеть от IP и User-Agent.
После reload появляются ошибки upstream
Проверьте, что группы app_a и app_b доступны, а имена в map совпадают с именами upstream. После изменения конфигурации сначала выполняйте nginx -t, затем reload. Не заменяйте проверку простым перезапуском сервиса.
Когда выбирать другой подход
Оставляйте распределение на Nginx, если нужно быстро проверить две версии публичной страницы и вы готовы отдельно настроить кеш и аналитику. Переносите назначение в приложение, если пользователь авторизован, должен видеть один вариант на нескольких устройствах или эксперимент влияет на операции с данными.
Не используйте A/B-тест через Nginx для миграций, где одна версия меняет формат записи в базе данных, очереди или API без обратной совместимости. Роутинг выбирает backend, но не синхронизирует схемы и не откатывает побочные эффекты.
Что настроить после запуска
Сначала прогоните тест на отдельном домене или URL с заголовком варианта, затем проверьте access log, кеш и реальные ответы обоих upstream. После этого зафиксируйте дату старта, долю трафика, основную метрику и правила остановки эксперимента. На проде отдельно наблюдайте ошибки 5xx и распределение по вариантам: отклонение от ожидаемой доли часто указывает не на результат теста, а на проблему с cookie, ботами или кешированием.
[1] Sticky-сегмент — группа пользователей, за которой закреплён один вариант эксперимента на заданный срок.
Создатель презентаций: Структура и текст каждого слайда: заголовки-утверждения, одна мысль на слайд, заметки докладчику.
Спросить за 150 ₽Источники: Официальная документация Nginx: ngx_http_split_clients_module, Официальная документация Nginx: ngx_http_proxy_module
Частые вопросы
Можно ли делать A/B-тест только через split_clients, без cookie?
Почему распределение получается не ровно 50/50?
Сломает ли A/B-тест proxy cache?
Что произойдёт, если пользователь удалит cookie?
Можно ли использовать эту схему для авторизованных пользователей?
Почему после изменения конфигурации часть запросов всё ещё идёт в старый вариант?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.