ZIM-НИЙ SAAALEЗимние скидки: до −50% на старт и −20% на продление
до 31.01.2026 Подробнее
Выберите продукт

Docker networking: hairpin NAT, loopback и MTU — от симптомов к диагностике

Если контейнер не ходит по доменному имени, не открывает сервис на собственном домене с той же машины или «подвисает» на HTTPS, часто виноваты hairpin NAT, loopback-маршруты и неверный MTU. Ниже — диагностика и исправления на Linux.
Docker networking: hairpin NAT, loopback и MTU — от симптомов к диагностике

Зачем разбираться в hairpin NAT, loopback и MTU

Docker на Linux обычно строит сеть через bridge-интерфейс docker0, правила iptables (иногда поверх nftables) и таблицу состояний conntrack. Пока всё работает, это выглядит как «контейнеры сами получили сеть». Но стоит появиться доступу к сервису по публичному домену с той же машины, VPN на хосте, нескольким интерфейсам или «подложке» с нестандартным MTU — начинаются симптомы, которые сложно связать с причиной.

Три частых источника боли:

  • Hairpin NAT (NAT loopback): контейнер обращается к сервису по публичному адресу/домену, а сервис опубликован на этом же хосте через -p. Пакет должен «выйти и вернуться» через NAT внутри одной машины.
  • Loopback/host loop: попытки достучаться из контейнера до сервисов хоста через 127.0.0.1 (не работает, потому что это loopback контейнера) и путаница вокруг host.docker.internal на Linux.
  • MTU: несоответствие MTU между путями (например, docker bridge 1500, а под ним туннель 1450) вызывает фрагментацию/blackhole, «подвисания» TLS и нестабильные загрузки.

Хорошая диагностика начинается с ответа на вопрос «на каком слое ломается»: DNS → маршрут → NAT/фильтрация → состояния (conntrack) → MTU/PMTUD. Если не перескакивать шаги, причина обычно находится быстро.

Быстрый чек-лист: что проверить в первую очередь

1) Понять, какой драйвер сети и где живут правила

Сначала убедитесь, что вы действительно в сценарии классического bridge (а не host, не macvlan и не overlay):

docker network ls
docker network inspect bridge

Обратите внимание на:

  • Subnet/Gateway (часто 172.17.0.0/16 и 172.17.0.1);
  • com.docker.network.bridge.name (имя linux bridge);
  • EnableIPMasquerade (включён ли masquerade).

2) Проверить DNS и маршрут из контейнера

Типичная история «container dns issues»: внутри контейнера домен резолвится в публичный IP, а сервис физически на том же хосте. Это частый триггер hairpin NAT.

docker exec -it CONTAINER sh -lc 'cat /etc/resolv.conf; getent hosts example.com || nslookup example.com || true'

Сразу же посмотрите маршрут и адреса:

docker exec -it CONTAINER sh -lc 'ip route; ip addr'

3) Посмотреть iptables и счётчики

Для диагностики важно смотреть не только правила, но и счётчики: попадают ли пакеты в нужную цепочку.

iptables -t nat -L -n -v
iptables -L -n -v

В фокусе обычно цепочки DOCKER, DOCKER-USER, а также POSTROUTING с MASQUERADE.

4) Проверить conntrack: нет ли переполнения

Если «иногда работает, иногда нет» при высокой параллельности, проверьте таблицу состояний.

sysctl net.netfilter.nf_conntrack_count net.netfilter.nf_conntrack_max
dmesg | tail -n 200

Ищите сообщения вида nf_conntrack: table full, dropping packet.

Hairpin NAT: как выглядит проблема и почему происходит

Hairpin NAT чаще всего проявляется так:

  • Контейнер не может обратиться к сервису по публичному домену, хотя «снаружи» домен работает.
  • С хоста curl по домену работает, из контейнера — таймаут, connection refused или попадание «не туда».
  • По внутренним адресам/именам (в одной сети Docker) всё ок, а «по боевому домену» — нет.

Причина: контейнер посылает трафик на публичный IP хоста. Docker делает DNAT при публикации порта (-p 80:80), но обратный путь и SNAT/маршрутизация в hairpin-сценарии могут не сложиться так, как вы ожидаете. В итоге соединение теряется на одном из этапов: PREROUTING/OUTPUT, FORWARD, POSTROUTING, conntrack, rp_filter.

Как проверить, что это именно hairpin NAT

1) Узнайте IP, на который резолвится домен из контейнера:

docker exec -it CONTAINER sh -lc 'getent hosts example.com'

2) Уточните публикации портов:

docker ps --format 'table {{.Names}}	{{.Ports}}'

3) Посмотрите DNAT-правила и счётчики в цепочке DOCKER:

iptables -t nat -L DOCKER -n -v

Если счётчики растут при запросе из контейнера — DNAT, скорее всего, срабатывает. Если нет — трафик может обходить ожидаемые цепочки (policy routing, VPN, альтернативные таблицы маршрутизации, уход через другой интерфейс).

Схема hairpin NAT: контейнер обращается к публичному IP хоста и возвращается на опубликованный порт через DNAT/SNAT

Наблюдение трафика: где рвётся SYN/SYN-ACK

Чтобы не лечить «наугад», снимите картину пакетами. На хосте удобно ловить сразу по всем интерфейсам:

tcpdump -ni any '(host PUBLIC_IP) and (tcp port 80 or tcp port 443)'

Если SYN уходит, DNAT происходит, а SYN-ACK не возвращается — в практике это почти всегда одна из трёх причин: SNAT/маршрутизация, фильтрация (FORWARD/DOCKER-USER) или rp_filter при асимметричном маршруте.

Типовой фикс: SNAT/MASQUERADE для подсети Docker

Docker обычно добавляет masquerade для подсети bridge «наружу», но hairpin-сценарии и сложные маршруты иногда требуют явного правила. Сначала проверьте, что происходит в POSTROUTING:

iptables -t nat -L POSTROUTING -n -v

Если вы уверены в диагностике и нужно быстро стабилизировать обратный путь, можно добавить правило masquerade для подсети Docker при выходе через внешний интерфейс (пример для eth0):

iptables -t nat -A POSTROUTING -s 172.17.0.0/16 -o eth0 -j MASQUERADE

Важно: это не универсальная «таблетка». Если у хоста несколько uplink-интерфейсов или трафик должен уходить по отдельным таблицам маршрутизации, правило нужно подгонять под вашу схему (и обязательно потом сделать его постоянным через ваш механизм управления фаерволом).

rp_filter и асимметричный маршрут

На серверах с несколькими интерфейсами, VPN или policy routing hairpin часто ломается из-за strict reverse path filtering. Проверьте:

sysctl net.ipv4.conf.all.rp_filter net.ipv4.conf.default.rp_filter

Для диагностики иногда временно переводят нужный интерфейс в режим 2 (loose) или 0 (off), но делайте это только понимая последствия для сетевой безопасности и с учётом периметра.

FastFox VDS
Облачный VDS-сервер в России
Аренда виртуальных серверов с моментальным развертыванием инфраструктуры от 195₽ / мес

Loopback и доступ к хосту из контейнера: почему 127.0.0.1 не работает

Классическая ошибка: приложение в контейнере обращается к http://127.0.0.1:5432, ожидая PostgreSQL на хосте. Но 127.0.0.1 внутри контейнера — это loopback самого контейнера, а не хоста.

Рабочие варианты доступа к хосту:

  • Адрес шлюза docker bridge (часто 172.17.0.1), если сервис на хосте слушает на этом интерфейсе.
  • Реальный адрес хоста в вашей сети (например, 10.0.0.10), если по политике так можно.
  • host.docker.internal — на Linux это не всегда «из коробки», но поддерживается современным Docker через host-gateway.

host.docker.internal на Linux: как включить правильно

Проверьте, резолвится ли имя в контейнере:

docker exec -it CONTAINER sh -lc 'getent hosts host.docker.internal || true'

Если нет — добавьте алиас на host gateway при запуске контейнера:

docker run --add-host=host.docker.internal:host-gateway -p 8080:80 IMAGE

Для docker-compose аналогично через extra_hosts:

services:
  app:
    extra_hosts:
      - "host.docker.internal:host-gateway"

Дальше в контейнере можно обращаться к сервисам хоста по host.docker.internal (и не путать это с публикацией портов через -p).

Порт «есть», но недоступен: частая ловушка на стороне хоста

Если сервис на хосте слушает только 127.0.0.1, то из контейнера он не будет доступен ни по 172.17.0.1, ни по адресу хоста. Нужно, чтобы сервис слушал на нужном интерфейсе (например, 0.0.0.0 или конкретный адрес).

Проверка на хосте:

ss -lntp

Тест из контейнера:

docker exec -it CONTAINER sh -lc 'nc -vz host.docker.internal 5432 || true'

MTU: когда «всё пингуется», но TLS и большие ответы зависают

Проблемы с MTU неприятны тем, что маленькие пакеты проходят, DNS работает, ping отвечает, но HTTPS-сессия зависает на рукопожатии, загрузки обрываются, стримы «замирают». Это типичный PMTUD/ICMP blackhole: путь требует меньший MTU, а ICMP-сообщения о необходимости фрагментации не доходят.

Откуда берётся несоответствие MTU:

  • Под хостом туннель (VXLAN, GRE, WireGuard, PPPoE) или «виртуальная» сеть с MTU меньше 1500.
  • VPN-клиент на хосте меняет дефолтный маршрут, и реальный путь до сервиса становится с меньшим MTU.
  • В облаке есть encapsulation, а docker bridge остался 1500.

Как быстро диагностировать MTU/fragmentation

1) Сравнить MTU на хосте и на docker bridge:

ip link show docker0
ip link show

2) Проверить путь до цели с DF (don’t fragment). Подбирайте размер в -s, чтобы найти порог:

ping -M do -s 1472 8.8.8.8

Если не проходит — снижайте (1460, 1452, 1412) и фиксируйте максимум. Для IPv4 ICMP ориентир такой: payload + 28 байт заголовков.

Проверка MTU и PMTUD командой ping с флагом DF и подбором размера пакета

Что делать: согласовать MTU для Docker

Если выяснили, что реальный MTU пути ниже (например, 1450), лучше настроить Docker так, чтобы контейнеры изначально отправляли пакеты подходящего размера. Обычно это делается через конфигурацию демона Docker (параметр mtu) и пересоздание сети/контейнеров, чтобы интерфейсы внутри контейнеров унаследовали новое значение.

Проверьте MTU внутри контейнера:

docker exec -it CONTAINER sh -lc 'ip link show eth0'

После изменения обязательно перепроверьте именно проблемные сценарии (TLS, загрузки, большие ответы API), а не только ping.

Если параллельно вы управляете доменами и TLS, полезно держать под рукой практику по wildcard и DNS-01: автоматизация wildcard SSL через DNS-01.

FastFox SSL
Надежные SSL-сертификаты
Мы предлагаем широкий спектр SSL-сертификатов от GlobalSign по самым низким ценам. Поможем с покупкой и установкой SSL бесплатно!

conntrack, iptables и «странная нестабильность»: что искать в инцидентах

Если DNS резолвит как ожидается, MTU согласован, а сценарии hairpin/loopback понятны, но соединения всё равно периодически рвутся — смотрите на хост как на сетевой узел: состояние, фильтрация и лимиты.

conntrack: симптомы и быстрые меры

Симптомы переполнения conntrack:

  • Новые соединения отваливаются на пиках.
  • В логах ядра появляются сообщения о заполнении таблицы.
  • Перезапуск части контейнеров «временно помогает» (снижается число активных состояний).

Проверка:

sysctl net.netfilter.nf_conntrack_count net.netfilter.nf_conntrack_max

Если nf_conntrack_count близок к nf_conntrack_max, дальше уже выбор стратегии: увеличить лимит (с учётом RAM), снизить параллелизм/ретраи приложения, пересмотреть таймауты, вынести часть трафика с этого узла.

DOCKER-USER: вы сами себе не перекрыли форвардинг

Docker рекомендует использовать цепочку DOCKER-USER для собственных правил фильтрации. Частая ошибка: поставить там DROP по умолчанию и забыть разрешить нужные направления. Тогда публикация порта «на хосте» может работать, а исходящие потоки контейнеров или межконтейнерный трафик начнут ломаться.

Проверка:

iptables -L DOCKER-USER -n -v

Если видите ранний DROP и счётчики растут — это сильный кандидат в причину.

Практические сценарии и решения

Сценарий A: контейнер ходит на домен, а домен указывает на этот же хост

Варианты (зависят от архитектуры):

  • Split-horizon DNS: «внутри» домен резолвится во внутренний адрес (контейнер/внутренний балансировщик), «снаружи» — в публичный.
  • Прямое обращение по имени сервиса в user-defined сети Docker.
  • Осознанно починить hairpin NAT на уровне NAT/SNAT и маршрутизации, если критично ходить именно по публичному домену.

Если домен обслуживает прод, не забывайте про жизненный цикл и риски просрочки: grace и redemption периоды продления домена.

Сценарий B: приложение в контейнере не видит сервис на хосте

Чаще всего причина одна из двух:

  • Используется 127.0.0.1 вместо адреса хоста/шлюза/host-gateway.
  • Сервис на хосте слушает только loopback и недоступен извне.

Решение: использовать host.docker.internal через --add-host=host.docker.internal:host-gateway и/или перевести сервис на прослушивание нужного интерфейса.

Сценарий C: «DNS ок, но HTTPS висит»

Это почти учебник по MTU/PMTUD. Проверьте MTU пути, нет ли блокировки ICMP (в том числе «fragmentation needed»), и согласуйте MTU на docker bridge и подложке. После исправления прогоните тесты на большие ответы (скачивание файла) и реальные TLS-сессии, а не только короткие запросы.

Мини-набор команд для разборов «в полях»

Набор, который удобно запускать по порядку, чтобы зафиксировать состояние и быстро локализовать слой проблемы:

docker ps
docker network ls
docker inspect CONTAINER --format '{{json .NetworkSettings.Networks}}'
docker exec -it CONTAINER sh -lc 'ip addr; ip route; cat /etc/resolv.conf'
iptables -t nat -L -n -v
iptables -L -n -v
ss -lntp
sysctl net.netfilter.nf_conntrack_count net.netfilter.nf_conntrack_max
tcpdump -ni any 'tcp port 80 or tcp port 443 or udp port 53'
ip link show docker0

Итоги: как мыслить про Docker-сеть, чтобы чинить быстрее

  • Hairpin NAT — доступ «к самому себе» через публичный адрес; смотрим DNAT/SNAT, маршрутизацию, rp_filter, conntrack.
  • Loopback — изоляция namespace: 127.0.0.1 всегда локален для текущего пространства; для хоста используйте gateway/host-gateway.
  • MTU — реальный путь пакета: если подложка меньше 1500, Docker с MTU 1500 создаёт плавающие проблемы, особенно на TLS.

Если действовать слоями (DNS → маршрут → NAT/iptables → conntrack → MTU), «таймауты только из контейнера» и «не открывается сервис по домену» обычно перестают быть мистикой и превращаются в понятную последовательность проверок.

Поделиться статьей

Вам будет интересно

PostgreSQL pg_hba.conf: настройка аутентификации и разбор типовых ошибок OpenAI Статья написана AI (GPT 5)

PostgreSQL pg_hba.conf: настройка аутентификации и разбор типовых ошибок

Разбираем pg_hba.conf в PostgreSQL: как читаются правила сверху вниз, чем отличаются peer, md5 и scram-sha-256, как безопасно откр ...
Postfix + Dovecot: как разбирать ошибки 552 и 554 и настраивать лимиты без боли OpenAI Статья написана AI (GPT 5)

Postfix + Dovecot: как разбирать ошибки 552 и 554 и настраивать лимиты без боли

Ошибки 552 и 554 в связке Postfix+Dovecot почти всегда связаны с лимитами или политиками: размер письма, квоты, число соединений, ...
SPF: include, redirect, ip4/ip6 и all — как собрать запись без PermError и лимита lookups OpenAI Статья написана AI (GPT 5)

SPF: include, redirect, ip4/ip6 и all — как собрать запись без PermError и лимита lookups

SPF-запись помогает почтовым серверам понять, какие источники могут отправлять письма от имени домена. Разберём include и redirect ...