Выберите продукт

Docker publish и firewall: почему порт не доступен и как чинить iptables/nftables, UFW и firewalld

Контейнер запущен и порт опубликован через Docker, но снаружи таймаут или не работает доступ по публичному IP с самого сервера? Разбираем publish, цепочки iptables/nftables, UFW/firewalld, NAT и hairpin NAT: быстрые проверки и рабочие решения.
Docker publish и firewall: почему порт не доступен и как чинить iptables/nftables, UFW и firewalld

Ситуация из практики: вы делаете docker run -p 8080:80 или прописываете ports: в Compose, контейнер «живой», локально на сервере всё открывается, а снаружи — таймаут. Или наоборот: снаружи доступно, но с самого сервера по своему публичному IP не работает (классический hairpin NAT). Ниже — пошаговая диагностика, что именно ломается: публикация, маршрутизация или firewall.

Как Docker делает publish: что меняется в сети

docker publish (опция -p / --publish) решает две задачи:

  • на хосте появляется «вход» на порт (часто это DNAT, иногда участвует userland-proxy);
  • пакеты перенаправляются на IP контейнера в docker-сети (обычно bridge docker0).

Технически Docker добавляет правила в NAT (DNAT в таблице nat) и правила в фильтрах для форвардинга к bridge. В iptables это обычно цепочки DOCKER, DOCKER-USER, DOCKER-ISOLATION-STAGE-1, DOCKER-ISOLATION-STAGE-2. В системах с nftables правила могут лежать в nft (через iptables-nft) или в нативных цепочках — зависит от дистрибутива и того, как настроен firewall.

«Порт опубликован» в Docker не равно «порт разрешён вашим firewall». Docker может создать DNAT, но строгая политика INPUT/FORWARD, UFW или зоны firewalld способны отрезать трафик раньше.

Симптомы: быстро классифицируем проблему

Перед тем как «крутить правила», определите, где именно обрыв:

  • Снаружи таймаут (SYN без ответа): порт не слушается на хосте, режется firewall/провайдером или неправильная публикация.
  • Снаружи RST: что-то отвечает, но, возможно, не тот сервис (конфликт порта) или приложение сразу закрывает соединение.
  • С хоста по 127.0.0.1 работает, по публичному IP нет: часто это hairpin NAT, rp_filter или политика фильтрации.
  • Снаружи работает, но из LAN по публичному IP нет: hairpin NAT уже на маршрутизаторе/балансировщике (не всегда лечится на Docker-хосте).

Если вы запускаете контейнеры на VDS, не забывайте про внешние фильтры: в панели провайдера/внешнем firewall порт может быть закрыт даже при «идеальных» правилах на сервере.

Схема: DNAT и форвардинг трафика к контейнеру при docker publish

Шаг 1. Проверяем, что Docker действительно опубликовал порт

Начните с очевидного — что опубликовано и на какие адреса:

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

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

  • 0.0.0.0:8080->80/tcp — слушает на всех IPv4-адресах;
  • 127.0.0.1:8080->80/tcp — доступно только локально на хосте (частая причина «снаружи не открывается»);
  • [::]:8080->80/tcp — публикация по IPv6; если клиенты идут по IPv4, а IPv4-биндинга нет, снаружи будет «не работает».

Проверьте, что на хосте реально есть слушающий сокет:

ss -lntp | grep -E '(:8080\s|:8080$)'

Если слушателя нет — Docker не поднял публикацию (контейнер не запущен, порт занят, ошибка в параметрах). Проверьте конфликты:

ss -lntp | grep -E '(:80\s|:80$|:8080\s|:8080$)'

Шаг 2. Проверяем путь: проблема внутри сервера или снаружи

С хоста проверьте доступ к опубликованному порту по разным адресам:

curl -sS -D- http://127.0.0.1:8080/ -o /dev/null
curl -sS -D- http://$(hostname -I | awk '{print $1}'):8080/ -o /dev/null

Если на 127.0.0.1 работает, а на IP интерфейса нет — это уже не проблема приложения, а фильтрация/маршрутизация/NAT.

С другой машины (в интернете или хотя бы другой подсети) проверьте TCP-рукопожатие:

nc -vz SERVER_IP 8080

Если nc висит с таймаутом, самые частые причины: публикация на 127.0.0.1, хостовый firewall, провайдерский firewall, либо трафик не доходит до сервера (ошибка маршрутизации/не тот IP).

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

Шаг 3. iptables: проверяем, что добавил Docker (NAT и FORWARD)

Даже если вы «открыли порт», Docker-трафик к контейнеру часто проходит через DNAT и цепочку FORWARD, а не через INPUT. Поэтому важно смотреть обе части: NAT и фильтр.

NAT: есть ли DNAT на контейнер

iptables -t nat -S | sed -n '1,200p'

Найдите цепочку DOCKER и правило, где внешний порт маппится на IP контейнера. Точечный поиск по порту:

iptables -t nat -S | grep -F -- '--dport 8080'

FILTER: где режется форвардинг (FORWARD и DOCKER-USER)

Если DNAT сработал, пакет дальше пойдёт через форвардинг на bridge-интерфейс. Блокировки часто сидят в FORWARD или в DOCKER-USER:

iptables -S FORWARD
iptables -S DOCKER-USER

Типичная ловушка: администратор добавил «deny all» в DOCKER-USER (что логично для базовой политики), но не добавил явное разрешение для нужных портов/подсетей. В результате Docker публикует порт, но трафик режется до контейнера.

Диагностика счётчиками

Счётчики помогают понять, доходит ли трафик до нужного места. Запустите тест подключения снаружи и смотрите, растут ли counters:

iptables -nvL DOCKER-USER
iptables -t nat -nvL DOCKER

Минимальный пример: разрешить вход на опубликованный порт через DOCKER-USER

Если у вас политика «всё запрещено» в DOCKER-USER, добавьте разрешение выше общего DROP. Пример для TCP/8080 (подставьте свой внешний интерфейс в -i при необходимости):

iptables -I DOCKER-USER 1 -p tcp --dport 8080 -j ACCEPT

Если хотите ограничить доступ только с доверенной подсети:

iptables -I DOCKER-USER 1 -p tcp -s 203.0.113.0/24 --dport 8080 -j ACCEPT

Важно: сохранение правил зависит от дистрибутива (iptables-persistent, systemd unit, свои скрипты). Не ограничивайтесь «применил и забыл».

Шаг 4. nftables: когда iptables «как будто есть», но реально рулит nft

В современных дистрибутивах iptables может быть лишь интерфейсом совместимости (iptables-nft), а реальные правила живут в nft. Поэтому диагностика должна включать просмотр nft-правил:

nft list ruleset | sed -n '1,200p'

Удобно искать нужный порт:

nft list ruleset | grep -nF 'dport 8080'

Частый сценарий «порт опубликован, но не доступен»: строгая политика в inet таблицах (input/forward) отбрасывает пакет раньше, чем он попадёт в правила, которые ожидает Docker. В nftables критичны hook и priority: правило может существовать, но стоять «позже» глобального drop.

Если nftables — ваш основной firewall, лучше явным образом описать: что разрешено на вход (input к хосту) и что разрешено на форвардинг к docker bridge. Не полагайтесь только на автогенерацию Docker.

Для более детального разбора цепочек Docker и типовых конфликтов nft/iptables см. материал Docker и firewall: как устроены цепочки iptables/nftables.

Просмотр nftables ruleset и iptables-цепочек Docker при диагностике порта

UFW и Docker: почему «ufw allow 8080/tcp» может не помочь

UFW обычно ориентирован на входящий трафик в цепь INPUT. А Docker-публикация часто превращает вход в DNAT + FORWARD к контейнеру. Поэтому «разрешил порт» в UFW не всегда лечит доступ к контейнеру.

Что проверить в первую очередь:

  • включён ли форвардинг: net.ipv4.ip_forward;
  • какова политика цепи FORWARD в iptables;
  • нет ли DEFAULT_FORWARD_POLICY="DROP" в настройках UFW.
ufw status verbose
sysctl net.ipv4.ip_forward
iptables -S FORWARD

Если UFW включён и FORWARD фактически «закрыт», Docker-трафик к контейнерам будет отваливаться даже при разрешённом порте на INPUT.

firewalld и Docker: зоны, masquerade и forward

В firewalld добавляется концепция зон и masquerade. Типовые причины недоступности опубликованных портов:

  • порт открыт в «не той» зоне (например, открыт в public, а интерфейс с публичным IP находится в drop);
  • запрещён forward между зонами (docker0 в отдельной зоне без разрешений);
  • в некоторых схемах не включён masquerade там, где он нужен для корректного обратного пути.

Проверьте состояние и привязку интерфейсов:

firewall-cmd --state
firewall-cmd --get-active-zones
firewall-cmd --zone=public --list-ports

Если интерфейс с публичным IP привязан не к той зоне, «открытие порта» не сработает так, как вы ожидаете.

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

Hairpin NAT (NAT loopback) — это сценарий, когда клиент находится «внутри» (на том же хосте или в той же L2/LAN), но обращается к сервису по внешнему (публичному) IP, и трафик должен «завернуться» обратно внутрь. В контексте Docker это часто выглядит так:

  • снаружи сервис доступен;
  • на хосте curl http://127.0.0.1:8080 работает, а curl http://PUBLIC_IP:8080 — нет;
  • контейнеры не могут ходить на сервис по публичному IP хоста, хотя по внутреннему адресу всё нормально.

Как быстро понять, hairpin ли это

Проверьте, как ядро маршрутизирует запрос к собственному публичному IP:

ip route get PUBLIC_IP

Проверьте rp_filter (строгая проверка обратного пути иногда ломает «нестандартные» схемы):

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

Что делать на практике

В реальной эксплуатации почти всегда достаточно одного из подходов:

  • для локальных healthcheck и проверок использовать 127.0.0.1 или IP интерфейса, а не публичный IP;
  • использовать split-horizon DNS: внутри сети имя резолвится во внутренний адрес;
  • если нужен именно hairpin, настраивать NAT/маршрутизацию осознанно и в одном месте (nftables или iptables, без «перетягивания» несколькими фреймворками).

Типовые причины «порт опубликован, но не доступен»

1) Публикация на localhost

В Compose/CLI легко случайно сделать биндинг на 127.0.0.1. Тогда снаружи порт не будет доступен.

docker run -p 127.0.0.1:8080:80 nginx

Для внешнего доступа нужен биндинг на 0.0.0.0 (по умолчанию) или конкретный публичный IP.

2) Хостовый firewall блокирует INPUT на порт

Если публикация реализована через userland-proxy или сервис слушает на хосте, блокировка будет в INPUT. Тогда открывайте порт именно в input/zone для публичного интерфейса и проверяйте counters на входящих правилах.

3) FORWARD=DROP и/или блок в DOCKER-USER

Самая частая причина при «правильной» безопасности: вы запрещаете форвардинг, и контейнерный DNAT не проходит. Лечится явными allow-правилами к нужным портам/подсетям и аккуратным базовым DROP.

4) Конфликт nftables приоритетов/хуков

Правила есть, но они срабатывают «после» общего drop из-за приоритетов. В nftables это встречается регулярно при кастомных inet-таблицах.

5) Внешний firewall провайдера

Признак: на хосте порт слушается и локально доступен, но counters на входящих правилах не растут при внешнем тесте. Тогда проверяйте фильтры на уровне провайдера/панели.

Виртуальный хостинг FastFox
Виртуальный хостинг для сайтов
Универсальное решение для создания и размещения сайтов любой сложности в Интернете от 95₽ / мес

Практический чек-лист диагностики (в порядке эффективности)

  1. Проверить биндинг в docker ps и слушатель в ss -lntp.

  2. Проверить доступ с хоста: 127.0.0.1, затем IP интерфейса, затем публичный IP (если нужно понять hairpin).

  3. С внешней машины проверить TCP (nc) и, если это HTTP, запрос (curl -v).

  4. Посмотреть DNAT: iptables -t nat -S и поиск по --dport.

  5. Проверить FORWARD и DOCKER-USER, затем counters (iptables -nvL).

  6. Если используется nftables: nft list ruleset, поиск по dport, проверить порядок (hook/priority) относительно drop.

  7. Если включён UFW: убедиться, что не ломает форвардинг.

  8. Если firewalld: проверить активные зоны интерфейсов и где открыт порт.

Что запомнить

Когда docker publish «не работает», чаще всего Docker как раз сделал свою часть (DNAT/цепочки), а проблема в том, что трафик к контейнерам идёт через NAT и FORWARD, а ваш firewall настроен под классические сервисы на хосте (INPUT). Разделяйте эти два мира, проверяйте цепочки и counters, и держите в голове hairpin NAT: он отлично объясняет «странности» с доступом по публичному IP с самого сервера.

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

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

Debian/Ubuntu: mount: wrong fs type, bad option, bad superblock — как быстро найти и исправить причину OpenAI Статья написана AI (GPT 5)

Debian/Ubuntu: mount: wrong fs type, bad option, bad superblock — как быстро найти и исправить причину

Ошибка mount: wrong fs type, bad option, bad superblock в Debian/Ubuntu может означать и простую опечатку в имени раздела, и пробл ...
Debian/Ubuntu: XFS metadata corruption и emergency read-only — пошаговое восстановление OpenAI Статья написана AI (GPT 5)

Debian/Ubuntu: XFS metadata corruption и emergency read-only — пошаговое восстановление

Если XFS-раздел внезапно стал доступен только для чтения, а сервер ушёл в emergency mode, главное — не спешить. Разберём безопасны ...
Debian/Ubuntu: как исправить Failed to fetch при apt update OpenAI Статья написана AI (GPT 5)

Debian/Ubuntu: как исправить Failed to fetch при apt update

Ошибка Failed to fetch при apt update в Debian и Ubuntu обычно связана не с самим APT, а с DNS, сетью, зеркалом, прокси, временем ...