Почему DNS в upstream внезапно становится проблемой
Если вы проксируете на сервисы по имени (например, api, backend, svc.namespace) и живёте в мире контейнеров, рано или поздно ловите симптом: Nginx «залип» на старом IP и упорно шлёт трафик не туда. В логах это часто выглядит как всплеск connect() failed, no live upstreams, 502/504, а потом «само проходит» после reload или рестарта.
Снаружи это похоже на сетевую нестабильность, но корень часто в том, как Nginx резолвит DNS для upstream и как он кэширует результат. В Docker и особенно в Kubernetes IP-адреса подов/контейнеров меняются регулярно, и если Nginx не обновляет DNS в рантайме, вы получаете stale DNS — устаревший адрес в памяти воркеров.
Дальше разберём: когда Nginx резолвит имя «один раз», когда умеет пере-резолвить, что реально делает resolver, как работает resolver valid, зачем часто ставят ipv6=off, и как собрать всё в рабочую конфигурацию для Docker и Kubernetes.
Как Nginx резолвит имена: «один раз при старте» против «в рантайме»
В статической инфраструктуре Nginx обычно достаточно модели: резолвим имя при чтении конфигурации (на старте или при reload) и дальше работаем с IP. В динамике контейнеров это становится ловушкой.
Ключевой момент: если вы указываете имя хоста в proxy_pass или в блоке upstream, Nginx не обязан автоматически обновлять его по DNS на каждое соединение. Часто без специальных приёмов он фиксирует результат на момент загрузки конфигурации.
Практически это выглядит так:
Имя в
upstreamкакserver backend:8080;— обычно резолвится при загрузке конфигурации. Дальше IP живёт в памяти. Если IP сменился — получите stale DNS доreload.Имя в
proxy_passчерез переменную (например,proxy_pass http://$backend;) — Nginx вынужден резолвить в рантайме, но только если настроен внутренний резолвер директивойresolver.
Отсюда правило: если адреса реально меняются (Docker/Kubernetes, autoscaling, rolling updates), вам почти всегда нужен resolver и такая схема конфигурации, где Nginx делает DNS-запросы в рантайме.
Если вы поднимаете Nginx как отдельный шлюз, в большинстве кейсов удобнее держать его на отдельной VDS: проще контролировать DNS, логи, метрики и не завязываться на жизненный цикл приложений в кластере.

Что делает директива resolver и почему без неё «динамики» не будет
resolver задаёт DNS-сервер(а), к которым Nginx будет обращаться, когда ему нужно резолвить имена в рантайме. Без resolver многие сценарии либо не будут работать (ошибки резолвинга), либо будут вести себя не так, как ожидается (не будет пере-резолва).
Базовый пример для Docker:
resolver 127.0.0.11 valid=10s ipv6=off;
resolver_timeout 2s;
Расшифровка:
127.0.0.11— встроенный DNS Docker (типично для пользовательских bridge-сетей).valid=10s— срок «годности» записи в кэше Nginx (не равен DNS TTL, см. ниже).ipv6=off— отключает AAAA/IPv6, чтобы не ловить задержки/таймауты при «полуживом» IPv6.resolver_timeout— сколько ждать ответ DNS, прежде чем считать запрос неуспешным.
Важно: resolver сам по себе не превращает обычный upstream в сервис-дискавери. Он нужен именно там, где Nginx в принципе выполняет DNS-запросы во время обработки запроса (например, когда proxy_pass использует переменную).
Про resolver valid: это не TTL, но часто важнее TTL
Ожидание «Nginx уважает DNS TTL» — частый источник сюрпризов. У Nginx свой кэш, и параметр valid задаёт, как долго он считает результат резолвинга актуальным в своей памяти. Это может отличаться от TTL у записи.
Ментальная модель такая: valid — это «как часто Nginx будет готов перепроверять DNS». Слишком большое значение увеличивает окно stale DNS (дольше верим старому IP). Слишком маленькое может привести к лишней нагрузке на DNS и сделать DNS скрытой точкой отказа.
Типовые стартовые значения для контейнерной среды:
2–10 секунд — если сервисы пересоздаются часто и важно быстрое восстановление без
reload.10–30 секунд — если инфраструктура стабильнее или вы экономите QPS к CoreDNS/Docker DNS.
Ещё нюанс: отрицательное кэширование. В отдельных ситуациях Nginx может кэшировать неуспешный резолв (NXDOMAIN/timeout) на время valid. Если сервис на мгновение пропал из DNS, большое valid может искусственно растянуть простой.
Почему в Docker/Kubernetes часто нужен ipv6=off
Смысл в предсказуемости. Если IPv6 фактически не настроен end-to-end (маршрутизация отсутствует, AAAA есть, но нерабочие, или попытки подключения дают таймаут), то резолв AAAA и соединения по IPv6 могут вносить задержки и «плавающие» 502/504.
Симптомы, которые часто исчезают после ipv6=off:
периодические 502/504 при живом IPv4;
длинные паузы перед
connect() failed;нестабильная латентность на первых запросах после смены адресов.
Если у вас корректный dual-stack и IPv6 действительно используется, отключать его не нужно. Но для типовых контейнерных сетей это практичная «страховка» от класса проблем «IPv6 объявлен, но не работает».
Рабочий паттерн: динамический DNS для proxy_pass через переменную
Самый простой и предсказуемый способ заставить Nginx пере-резолвить имя — использовать переменную в proxy_pass. Это заставляет Nginx делать DNS-запросы в рантайме и применять кэш по valid.
Пример (в Docker или в Kubernetes, когда вы идёте по DNS-имени сервиса):
server {
listen 80;
resolver 127.0.0.11 valid=10s ipv6=off;
resolver_timeout 2s;
location / {
set $upstream_host backend;
proxy_pass http://$upstream_host:8080;
proxy_connect_timeout 2s;
proxy_read_timeout 60s;
}
}
Компромисс: часть оптимизаций теряется, а если вам нужна сложная балансировка, keepalive-пулы и тонкая политика ретраев, иногда удобнее идти другим путём (см. следующий раздел).
Если Nginx живёт рядом с сайтом/приложением и вы не хотите управлять ОС и обновлениями, часто удобнее вынести фронт на виртуальный хостинг и держать конфиги в понятной схеме деплоя через reload по изменениям.
Если нужен upstream с балансировкой: что можно и что нельзя
Ожидание «достаточно написать в upstream имя, и Nginx сам будет следить за DNS» обычно не оправдывается. Пример, который чаще всего даёт stale DNS:
upstream app {
server backend:8080;
}
При смене IP у backend Nginx продолжит использовать старый адрес до перечитывания конфигурации.
Что делать на практике:
Стараться проксировать на стабильный endpoint. В Kubernetes это обычно Service (ClusterIP) или балансировщик, где IP стабилен, а смена endpoint-ов происходит ниже уровня Nginx.
Не ходить напрямую на IP подов без необходимости: IP меняются, и вы постоянно будете ловить отвал на уровне L4.
Если список адресов должен меняться, выбирайте управляемую схему: шаблонизация конфига + безопасный
reload, либо внешний сервис-дискавери, который генерирует актуальный список бекендов.
В «чистом Nginx» наиболее универсально: опираться на стабильные endpoints и корректно настроить таймауты/ретраи, вместо попыток сделать из Nginx полноценный discovery-движок.
Docker: какой DNS указывать в resolver
В Docker для пользовательских bridge-сетей встроенный DNS обычно слушает 127.0.0.11. Это хороший кандидат для resolver, потому что он знает имена контейнеров/сервисов в рамках сети.
Минимальный практичный набор:
resolver 127.0.0.11 valid=10s ipv6=off;
resolver_timeout 2s;
Если внутри контейнера используется кастомный /etc/resolv.conf (например, корпоративный DNS), указывать нужно те DNS-серверы, которые реально доступны из того же network namespace, где работает Nginx.
Kubernetes: DNS для Nginx и типовые ловушки
В Kubernetes резолвинг обычно обслуживает CoreDNS. При этом директива resolver не «подхватывает» системный резолвер автоматически: вы жёстко задаёте адрес(а), по которым Nginx будет слать DNS-запросы.
Типовая ловушка: вручную прописали IP CoreDNS, затем сменили кластер или диапазон — и Nginx перестал резолвить. Поэтому на практике часто делают так:
не задают
resolverстатическим IP без необходимости;или параметризуют конфиг (ConfigMap/шаблоны), чтобы IP DNS был управляемым;
или проектируют маршрутизацию через стабильные сервисные имена, чтобы частый пере-резолв не требовался.
Кейс «Nginx в поде ходит по DNS на сервис, у которого меняются endpoints» — нормальный. Тогда как раз помогают адекватный valid и короткий resolver_timeout.
Если параллельно вы автоматизируете выпуск сертификатов и завязаны на DNS, полезно держать под рукой материал про автоматизацию wildcard SSL через DNS-01: там часто всплывают схожие вопросы про доступность резолвера и задержки обновления записей.

Диагностика: как понять, что это stale DNS и виноват резолвинг
Задача диагностики — ответить на два вопроса: какой IP сейчас отдаёт DNS и какой IP использует Nginx.
Проверяем DNS из окружения Nginx
В контейнере/поде (именно там, где работает Nginx) выполните:
getent hosts backend
nslookup backend
Если утилит нет (часто в минимальных образах), используйте отладочный контейнер/ephemeral container или отдельный диагностический pod.
Проверяем, куда реально пытается подключиться Nginx
Начните с вывода активной конфигурации:
nginx -T
И проверьте, нет ли у вас схемы, фиксирующей адреса на старте (типичный upstream со статическим server name:port без рантайм-резолвинга).
Дальше — активные соединения и попытки:
ss -tnp | grep nginx
Вы увидите реальные IP, к которым Nginx устанавливает TCP-сессии. Сверьте с тем, что показывает DNS. Если не совпадает — у вас stale DNS/кэш на стороне Nginx или промежуточного слоя.
Если видите, что DNS уже отдаёт новый IP, а Nginx продолжает стучаться в старый, почти всегда дело в том, что резолв был сделан «один раз», либо кэш живёт дольше, чем вы думаете (через
validили через схему использования имени).
Тюнинг: как выбрать valid, таймауты и не устроить DNS-шторм
valid — это компромисс: чем меньше значение, тем быстрее реакция на смену IP, но тем выше QPS к DNS и тем сильнее вы зависите от здоровья резолвера.
Для старта в контейнерной среде обычно достаточно:
valid=10sкак базовое значение;resolver_timeout 2s(иногда1s), чтобы деградация DNS не «съедала» бюджет таймаутов;короткий
proxy_connect_timeout(1–3s), чтобы быстрее отваливаться от мёртвого IP.
Если у вас много воркеров и высокий RPS, слишком агрессивный valid в сочетании с большим числом разных имён upstream может создать заметную нагрузку на CoreDNS/Docker DNS. Следите за метриками резолвера и при необходимости увеличивайте valid или сокращайте число уникальных имён, которые приходится резолвить.
Чек-лист: стабильная схема для Docker/Kubernetes
По возможности проксируйте на стабильный endpoint (в Kubernetes чаще всего Service), а не на IP подов.
Если нужны динамические адреса — используйте
resolverи переменную вproxy_pass, чтобы Nginx резолвил в рантайме.Подберите
resolver validпод частоту изменений и допустимое окно «залипания».Если IPv6 не настроен end-to-end — включайте
ipv6=off, чтобы убрать задержки на AAAA/IPv6 connect.При инциденте сравнивайте: что отдаёт DNS из этого окружения и куда реально соединяется Nginx (логи,
ss).
Итог
Проблемы DNS в Nginx чаще всего всплывают в динамической инфраструктуре: Docker, Kubernetes, автоскейлинг и частые пересоздания. Чтобы не лечить отвалившиеся бэкенды рестартом, осознанно настройте resolver, подберите resolver valid как управляемый кэш, и используйте ipv6=off там, где IPv6 фактически отсутствует. Тогда окно stale DNS становится контролируемым, а поведение прокси — предсказуемым.


