В Nginx тема DNS для upstream всплывает не в теории, а в самый неподходящий момент: контейнер пересоздали — IP поменялся, балансировщик отдал другой адрес, сервис переехал, а Nginx продолжает ходить «в никуда». В логах появляются upstream timed out, connect() failed, а иногда конфиг вообще не стартует с ошибкой host not found in upstream.
Ниже — практическая шпаргалка: как устроен резолвинг upstream в Nginx, что именно называют «stale DNS cache» применительно к Nginx, как правильно настроить resolver и valid, как жить с Docker DNS и systemd-resolved, и что проверять при подозрениях на DNS-проблемы и таймауты upstream.
Почему Nginx «не видит» новый IP: модель резолвинга
Ключевая особенность: Nginx не является полноценным DNS-клиентом «как браузер». Во многих случаях он резолвит имена на этапе чтения конфигурации и затем работает с полученными IP, не переопрашивая DNS на каждый запрос.
Отсюда два типовых сценария:
- Смена IP у backend по DNS (запись A/AAAA обновилась, контейнер пересоздан) — Nginx продолжает использовать старый адрес до тех пор, пока не произойдёт повторное разрешение имени (иногда только после
reload). - Nginx не стартует с ошибкой
upstream host not found— если имя не резолвится в момент загрузки конфига, Nginx может завершиться, даже если DNS «починится» через 10 секунд.
Типовые симптомы и как их отличить от «просто таймаутов»
Перед тем как крутить DNS, полезно понять, какой именно режим отказа вы наблюдаете. Ниже — наиболее частые сообщения и их смысл.
1) upstream host not found и nginx does not resolve host
Обычно это ошибка на этапе загрузки конфигурации. Nginx пытается резолвить имя из upstream или из proxy_pass, но DNS не отвечает или отвечает NXDOMAIN. Часто встречается в контейнерных средах при старте Nginx раньше, чем поднялся DNS/сеть/сервис.
2) connect() failed (111: Connection refused) / no route to host
DNS уже отдал IP, но на адресе ничего не слушает, либо маршрут не существует. Это может быть как реальная недоступность сервиса, так и «залипание» на старом IP после пересоздания контейнера/VM.
3) upstream timed out и 504
Это не всегда DNS. Но DNS-проблемы могут маскироваться под таймауты, если Nginx упорно подключается к старому адресу, который стал «чёрной дырой» (SYN уходит, ответа нет).
Если таймауты начались одновременно со сменой IP/релизом/пересозданием контейнеров и исчезают после
nginx -s reload, очень вероятно, что причина именно в резолвинге upstream.

Как заставить Nginx пере-резолвить upstream: resolver + переменная в proxy_pass
Самый надёжный приём: перевести целевой хост в режим «динамического» разрешения имени. В Nginx это обычно делается двумя шагами:
- задать DNS-серверы директивой
resolver; - использовать переменную в
proxy_pass(иначе имя может быть резолвлено один раз при парсинге конфига).
Базовый пример: резолвить имя периодически
resolver 1.1.1.1 8.8.8.8 valid=30s;
resolver_timeout 2s;
server {
listen 80;
location / {
set $backend "app.example.internal";
proxy_pass http://$backend:8080;
proxy_connect_timeout 2s;
proxy_read_timeout 30s;
}
}
Что здесь важно:
valid=30s— это срок, в течение которого Nginx считает результат резолвинга пригодным. По истечении срока Nginx сделает новый DNS-запрос при необходимости.resolver_timeout— защита от зависания на проблемном DNS.proxy_pass http://$backend:8080— наличие переменной заставляет Nginx резолвить имя в рантайме (вместо единоразового разрешения при загрузке конфига).
Что значит valid на практике
Если вы ожидаете, что Nginx будет строго следовать TTL записи A/AAAA, вас может ждать сюрприз: в этой схеме вы управляете TTL кэша Nginx через valid. Реальная цель — подобрать компромисс между:
- скоростью реакции на смену IP (меньше
valid); - нагрузкой на DNS и стабильностью (больше
valid).
Для сервисов в Docker/Kubernetes, где IP может меняться часто, обычно разумны значения 5–30 секунд. Для более статичных upstream — 30–300 секунд.
Почему upstream { server name; } не всегда решает задачу
Многие начинают с «красивого» блока upstream:
upstream backend {
server app.example.internal:8080;
keepalive 32;
}
server {
location / {
proxy_pass http://backend;
}
}
Проблема в том, что без дополнительных механизмов Nginx может разрешить имя один раз и держать IP неизменным. В итоге смена DNS не приводит к смене реального адреса назначения, и вы получаете классическую историю про stale DNS cache.
Если вам критично именно «следовать DNS», чаще проще и прозрачнее идти через resolver + переменную в proxy_pass, чем надеяться на поведение резолвинга внутри upstream.
Nginx reload и DNS: когда перезагрузка действительно помогает
Запрос «nginx reload dns» популярен по простой причине: reload часто действительно «лечит» ситуацию, потому что Nginx перечитывает конфигурацию и заново резолвит статические имена.
Но у этого подхода есть минусы:
- Если DNS «дрожит» или upstream часто меняет IP, вам придётся делать
reloadслишком часто. - В момент проблем с DNS вы рискуете получить отказ при перечитывании конфига (если имя не резолвится) и усложнить инцидент.
- Это костыль: вы лечите симптомы, а не причину.
Рекомендация: использовать reload как аварийную меру, а для постоянного решения — включать динамический резолвинг через resolver.
Docker DNS + Nginx: почему «всё работает, пока не пересоздали контейнер»
Частый кейс: Nginx работает в контейнере (или на хосте) и проксирует на сервис по имени контейнера/сервиса. В Docker Compose имя сервиса резолвится через встроенный DNS Docker. При пересоздании контейнера IP меняется, а имя остаётся тем же.
Но Nginx может сохранить старый IP, если резолвил имя только при старте. Итог — «после деплоя 502/504 до перезапуска nginx».
Практический рецепт в контейнерных сетях:
- в
resolverуказывать DNS-сервер Docker (часто это127.0.0.11внутри контейнера); - использовать переменную в
proxy_pass; - ставить небольшой
valid(например, 10s).
Пример (внутри контейнера Nginx):
resolver 127.0.0.11 valid=10s;
resolver_timeout 2s;
server {
listen 80;
location / {
set $backend "api";
proxy_pass http://$backend:8080;
}
}
Если вы не уверены, какой DNS использует контейнер, проверьте /etc/resolv.conf внутри него.
Если параллельно вы обслуживаете сайты/приложения на внешнем периметре, держите под рукой и доменную часть: сроки продления и переносы иногда всплывают в виде «внезапного NXDOMAIN». Полезный разбор — как устроен перенос домена: EPP, DNS и типовые ошибки.
systemd-resolved и Nginx: где ломается цепочка
На современных дистрибутивах часто включён stub-resolver, и в /etc/resolv.conf</code прописан <code>127.0.0.53. Для обычных приложений это нормально, но для Nginx есть нюансы:
resolverв Nginx — это не чтение/etc/resolv.conf. Если вы не указалиresolver, Nginx может вести себя как «статический» резолвер на старте.- Если вы указали
resolver 127.0.0.53, важно, чтобы Nginx действительно мог обращаться к этому адресу в своём неймспейсе (особенно если Nginx в контейнере).
На хостовой установке обычно рабочий вариант: либо указывать реальные DNS вашего окружения (смотрите resolvectl dns), либо оставить 127.0.0.53 если вы уверены, что Nginx запускается на хосте и stub доступен.
Диагностика: что проверять, когда «upstream timeouts» похожи на DNS
Ниже — короткий прикладной чеклист, который помогает быстро отделить DNS от TCP и проблем приложения.
Шаг 1. Убедиться, что имя резолвится там, где работает Nginx
getent hosts app.example.internal
nslookup app.example.internal
dig +short app.example.internal A
dig +short app.example.internal AAAA
Если Nginx в контейнере — выполняйте команды внутри контейнера.
Шаг 2. Сравнить IP из DNS и фактические подключения Nginx
ss -tnp | grep nginx
ss -tn dst :8080
Смысл: увидеть, к каким адресам Nginx реально подключается. Если DNS уже показывает новый IP, а Nginx продолжает стучаться в старый — это и есть эффект stale DNS cache.
Шаг 3. Проверить логи Nginx с деталями upstream
Если у вас в access log есть $upstream_addr, $upstream_connect_time, $upstream_response_time — отлично. Если нет, на время диагностики удобно включить расширенный формат логов (или отдельный лог для проблемного location):
log_format upstream_debug '$remote_addr - $host "$request" $status '
'upstream=$upstream_addr '
'uct=$upstream_connect_time urt=$upstream_response_time '
'uht=$upstream_header_time rt=$request_time';
Дальше смотрим: upstream=старый_IP:порт при том, что DNS уже другой — диагноз почти готов.
Шаг 4. Понять, что именно не так: DNS, TCP или приложение
Быстрые проверки:
curl -sS -v --max-time 2 http://NEW_IP:8080/
curl -sS -v --max-time 2 http://OLD_IP:8080/
Если на новом IP сервис отвечает, а на старом нет — проблема не в приложении, а в том, что Nginx не обновил адрес назначения.

Антипаттерны: как делают хуже
- Ставить огромный
valid«чтобы реже трогать DNS», а потом удивляться, что Nginx держит старый IP часами. - Лечить всё
nginx -s reloadи не разбираться с динамическим резолвингом. Это повышает операционные риски. - Смешивать несколько источников правды: в контейнере резолвить через внешний DNS, который не знает внутренних имён Docker/overlay-сети.
- Игнорировать IPv6: DNS может отдавать AAAA, и Nginx попытается ходить по IPv6, где нет маршрута. Если у вас dual-stack «не везде», обязательно проверяйте AAAA-ответы.
Рекомендуемые рабочие схемы
Схема A: статичный backend (редко меняется)
Можно оставить классический upstream с IP или с именем, но понимать: смена адреса потребует reload. Для критичных систем лучше всё равно предусмотреть динамический резолвинг.
Схема B: backend по имени, IP меняется (контейнеры/автоскейлинг)
Используйте resolver и переменную в proxy_pass. Подберите valid 5–30 секунд. Это обычно самый предсказуемый вариант.
Схема C: несколько upstream и внешний healthcheck
Nginx (open source) не делает «настоящие» активные healthchecks для HTTP как коробочное решение, поэтому при динамическом DNS особенно важно иметь внешнюю проверку доступности сервисов и наблюдать метрики 502/504, а также времена коннекта.
Короткий итог
Если у вас всплывают кейсы уровня «nginx upstream dns», «stale dns cache», «nginx does not resolve host» или «upstream host not found», чаще всего проблема в том, что имя резолвится не так, как вы ожидаете. Практическое лечение — задать явный resolver и организовать динамический резолвинг через переменную в proxy_pass, а затем подтвердить изменения диагностикой: сравнить DNS-ответы и реальные $upstream_addr в логах.
Когда это настроено, смена IP у backend перестаёт быть «магией», а становится предсказуемым процессом с понятным временем реакции, зависящим от valid и стабильности DNS.


