Docker обходит UFW: 4 проверенных решения для вашего VPS

9 мин чтения·Matthieu|

Docker напрямую управляет iptables и игнорирует правила UFW. Порты контейнеров открыты в интернет даже при активном ufw deny. Вот четыре решения с компромиссами, каждое проверено сканированием с внешнего хоста.

Ваш файрвол UFW врёт. Если вы запускаете Docker на VPS с включённым UFW, каждый опубликованный порт контейнера открыт в интернет. Команда ufw deny 8080 ничего не даёт. Docker полностью обходит UFW.

Этот туториал показывает проблему в действии, а затем разбирает четыре решения с разными компромиссами. Каждое решение включает шаг проверки: сканирование порта с внешнего хоста, чтобы убедиться, что он действительно заблокирован. Не просто проверка ufw status.

Руководство работает на Ubuntu 24.04 и Debian 12. Оба используют уровень совместимости iptables по умолчанию, поэтому одни и те же решения подходят для обоих. Debian 12 использует nftables как backend по умолчанию, но UFW и Docker взаимодействуют через интерфейс iptables, который прозрачно маппится на nftables.

Что нужно: VPS с установленным Docker, включённым UFW и SSH-доступом. Вторая машина (ваш ноутбук или другой сервер) для внешнего сканирования портов.

Почему Docker обходит правила файрвола UFW?

Docker пишет правила iptables в таблицы nat и filter для маршрутизации трафика в контейнеры. UFW управляет только цепочкой INPUT. Когда пакет приходит на опубликованный порт контейнера, NAT-правила Docker перенаправляют его через цепочку FORWARD до того, как правила INPUT UFW смогут его увидеть. Пакет никогда не попадает в UFW. Это значит, что ufw deny не действует на порты, опубликованные Docker.

Вот путь пакета для запроса на порт 8080 вашего VPS, где контейнер опубликован через -p 8080:80:

  1. Пакет приходит на сетевой интерфейс
  2. Попадает в цепочку PREROUTING таблицы nat
  3. DNAT-правило Docker перезаписывает адрес назначения на IP контейнера (например, 172.17.0.2:80)
  4. Пакет переходит в цепочку FORWARD таблицы filter (не INPUT)
  5. Цепочка DOCKER принимает перенаправленный пакет
  6. Пакет достигает контейнера

UFW его не видит, потому что следит только за цепочкой INPUT. Пакет идёт по пути FORWARD.

Это не баг. Docker нужен контроль над iptables для работы сети контейнеров. Но это создаёт серьёзную дыру в безопасности, если вы думали, что UFW защищает ваш сервер.

Как проверить, что Docker обходит мой файрвол UFW?

Перед применением любого исправления убедитесь в проблеме сами. Запустите тестовый контейнер, который отдаёт HTTP на порту 8080:

docker run -d --name ufw-test -p 8080:80 nginx:alpine

Проверьте, что UFW активен и по умолчанию блокирует входящий трафик:

sudo ufw status verbose

В выводе должно быть Default: deny (incoming). Теперь явно заблокируйте порт 8080:

sudo ufw deny 8080

Убедитесь, что UFW показывает порт как заблокированный:

sudo ufw status | grep 8080
8080                       DENY        Anywhere
8080 (v6)                  DENY        Anywhere (v6)

UFW показывает порт как заблокированный. Теперь проверьте с вашей локальной машины (не с сервера):

curl -s -o /dev/null -w "%{http_code}" http://YOUR_SERVER_IP:8080
200

Ответ 200. Контейнер полностью доступен из интернета, несмотря на блокировку порта в UFW. Если на вашей локальной машине установлен nmap:

nmap -p 8080 YOUR_SERVER_IP
PORT     STATE SERVICE
8080/tcp open  http-proxy

Порт открыт. UFW его не защищает.

Удалите правило deny (дальше вы примените настоящее исправление):

sudo ufw delete deny 8080

Оставьте тестовый контейнер запущенным. Он пригодится для проверки каждого решения.

Какое исправление Docker UFW выбрать?

Есть четыре решения. У каждого свои компромиссы. Выберите подходящее для вашей конфигурации.

Решение Влияние на сеть Переживает перезагрузку Совместимо с Compose Сложность Подходит для
Цепочка DOCKER-USER Нет Да Да Средняя Полный контроль, несколько публичных портов
Утилита ufw-docker Нет Да Да Низкая Автоматическое управление, динамические контейнеры
iptables=false Ломает сеть между контейнерами, нет интернета из контейнеров Да Да Низкая Изолированные одиночные контейнеры (редко)
Привязка к 127.0.0.1 Нет Да Да Низкая Сервисы за reverse proxy

Быстрый выбор:

  1. Все контейнеры за reverse proxy (Nginx, Traefik, Caddy)? Используйте привязку к 127.0.0.1. Самый простой вариант.
  2. Нужны контейнеры, доступные извне на определённых портах? Используйте цепочку DOCKER-USER для ручного контроля или ufw-docker для автоматизации.
  3. Запускаете контейнеры, которые не должны иметь доступ к сети? Используйте iptables=false, но учитывайте последствия.

Как исправить обход UFW Docker с помощью цепочки DOCKER-USER?

Цепочка DOCKER-USER — это заглушка, которую Docker оставляет пустой для администраторов. Правила, которые вы сюда добавляете, обрабатываются до собственных правил пересылки Docker. Вставив правила интеграции с UFW в эту цепочку через /etc/ufw/after.rules, вы заставите Docker-трафик проходить через UFW.

Этот метод даёт полный контроль. Работает и с docker run, и с Docker Compose. Переживает перезагрузки, потому что правила загружаются при старте UFW.

Откройте /etc/ufw/after.rules:

sudo cp /etc/ufw/after.rules /etc/ufw/after.rules.bak
sudo nano /etc/ufw/after.rules

Добавьте следующий блок в конец файла, после существующей строки COMMIT:

# BEGIN UFW AND DOCKER
*filter
:ufw-user-forward - [0:0]
:ufw-docker-logging-deny - [0:0]
:DOCKER-USER - [0:0]
-A DOCKER-USER -j ufw-user-forward

-A DOCKER-USER -m conntrack --ctstate RELATED,ESTABLISHED -j RETURN
-A DOCKER-USER -m conntrack --ctstate INVALID -j DROP
-A DOCKER-USER -i docker0 -o docker0 -j ACCEPT

-A DOCKER-USER -j RETURN -s 10.0.0.0/8
-A DOCKER-USER -j RETURN -s 172.16.0.0/12
-A DOCKER-USER -j RETURN -s 192.168.0.0/16
-A DOCKER-USER -j ufw-docker-logging-deny -m conntrack --ctstate NEW -d 10.0.0.0/8
-A DOCKER-USER -j ufw-docker-logging-deny -m conntrack --ctstate NEW -d 172.16.0.0/12
-A DOCKER-USER -j ufw-docker-logging-deny -m conntrack --ctstate NEW -d 192.168.0.0/16

-A DOCKER-USER -j RETURN

-A ufw-docker-logging-deny -m limit --limit 3/min --limit-burst 10 -j LOG --log-prefix "[UFW DOCKER BLOCK] "
-A ufw-docker-logging-deny -j DROP

COMMIT
# END UFW AND DOCKER

Что делает этот блок:

  • -A DOCKER-USER -j ufw-user-forward: отправляет Docker-трафик сначала через правила пересылки UFW
  • conntrack --ctstate RELATED,ESTABLISHED: разрешает обратный трафик для установленных соединений (сохраняет существующие соединения после изменения правил)
  • conntrack --ctstate INVALID: отбрасывает некорректные пакеты
  • -i docker0 -o docker0: разрешает связь между контейнерами на одном bridge-интерфейсе
  • Строки -j RETURN -s разрешают трафик из приватных подсетей
  • Новые соединения (ctstate NEW) к подсетям Docker извне логируются и отбрасываются
  • Правило логирования ограничивает частоту до 3 сообщений в минуту, чтобы не засорять логи

Перезагрузите UFW для применения новых правил:

sudo ufw reload

Проверьте, что правила загружены:

sudo iptables -L DOCKER-USER -n -v

Вы должны увидеть ваши новые правила в выводе цепочки. Если цепочка показывает только правило RETURN по умолчанию, блок after.rules не загрузился корректно. Проверьте синтаксис файла.

Разрешить конкретный порт контейнера через UFW

С активной цепочкой DOCKER-USER все порты контейнеров по умолчанию заблокированы для внешнего доступа. Чтобы разрешить конкретный порт, нужно указать IP контейнера и порт контейнера, а не порт хоста. Причина: DNAT-правило Docker в цепочке PREROUTING перезаписывает адрес назначения до того, как пакет попадёт в цепочку FORWARD. На момент проверки вашего правила адрес назначения — это внутренний адрес контейнера.

Сначала узнайте IP контейнера:

docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' ufw-test
172.17.0.2

Затем разрешите трафик к этому контейнеру:

sudo ufw route allow proto tcp from any to 172.17.0.2 port 80

Убедитесь, что UFW показывает правило маршрутизации:

sudo ufw status | grep "ALLOW FWD"
172.17.0.2 80/tcp          ALLOW FWD   Anywhere

Учтите: правило ссылается на IP контейнера, который может измениться при перезапуске. Используйте утилиту ufw-docker (следующий раздел) для автоматического отслеживания IP. Ручной подход DOCKER-USER лучше подходит, когда вы сами управляете IP контейнеров (статические IP в Docker Compose или скрипты обновления правил).

Проверка с внешнего хоста

С вашей локальной машины проверьте разрешённый порт:

curl -s -o /dev/null -w "%{http_code}" http://YOUR_SERVER_IP:8080
200

Порт доступен, потому что вы явно разрешили пересылку к контейнеру. Теперь проверьте порт, который не разрешали (например, если бы у вас был контейнер на порту 9090):

nmap -p 9090 YOUR_SERVER_IP
PORT     STATE    SERVICE
9090/tcp filtered unknown

filtered означает, что файрвол молча отбрасывает пакеты. Цепочка DOCKER-USER работает.

Как использовать утилиту ufw-docker для управления правилами файрвола контейнеров?

Утилита chaifeng/ufw-docker автоматизирует настройку цепочки DOCKER-USER и предоставляет команды для разрешения или блокировки портов контейнеров. Она изменяет /etc/ufw/after.rules за вас и добавляет CLI для управления правилами по контейнерам.

Установка:

sudo wget -O /usr/local/bin/ufw-docker \
  https://github.com/chaifeng/ufw-docker/raw/master/ufw-docker
sudo chmod +x /usr/local/bin/ufw-docker

Проверьте скрипт перед запуском. Это shell-скрипт, который можно прочитать командой cat /usr/local/bin/ufw-docker.

Запустите установку для изменения /etc/ufw/after.rules:

sudo ufw-docker install

Это добавит правила цепочки DOCKER-USER, аналогичные предыдущему разделу, включая фильтрацию на основе conntrack и логирование. Также будет изменён after6.rules для IPv6. Перезагрузите UFW:

sudo ufw reload

Проверьте установку:

sudo ufw-docker check

Управление портами контейнеров

Разрешить внешний доступ к порту 80 контейнера с именем web:

sudo ufw-docker allow web 80/tcp

Посмотреть правила контейнера:

sudo ufw-docker list web

Удалить правило:

sudo ufw-docker delete allow web 80/tcp

Показать все правила файрвола Docker:

sudo ufw-docker status

Пример Docker Compose с ufw-docker

services:
  web:
    image: nginx:alpine
    container_name: web
    ports:
      - "8080:80"
    restart: unless-stopped

Запустите стек, затем разрешите порт:

docker compose up -d
sudo ufw-docker allow web 80/tcp

Обратите внимание: команда ufw-docker использует порт контейнера (80), а не порт хоста (8080). Утилита разрешает маппинг автоматически.

Альтернатива: ufw-docker-automated

Проект shinebayar-g/ufw-docker-automated использует другой подход. Он работает как Docker-контейнер, слушает события Docker API и автоматически создаёт правила UFW при запуске или остановке контейнеров. Это решает ограничение утилиты ufw-docker: когда контейнер перезапускается и получает новый IP, старое правило становится недействительным.

С ufw-docker-automated вы добавляете лейблы к контейнерам:

services:
  web:
    image: nginx:alpine
    ports:
      - "8080:80"
    labels:
      - "UFW_MANAGED=TRUE"
    restart: unless-stopped

Утилита отслеживает контейнеры с лейблом UFW_MANAGED=TRUE и автоматически создаёт соответствующие правила UFW. Также поддерживается UFW_ALLOW_FROM для ограничения доступа конкретными IP или CIDR-диапазонами.

Проверка с внешнего хоста

После разрешения порта через ufw-docker проверьте с вашей локальной машины:

curl -s -o /dev/null -w "%{http_code}" http://YOUR_SERVER_IP:8080
200

Проверьте порт, которого нет в списке, чтобы убедиться в блокировке:

nmap -p 9090 YOUR_SERVER_IP
PORT     STATE    SERVICE
9090/tcp filtered unknown

Что будет, если отключить управление iptables в Docker?

Установка "iptables": false в конфигурации демона Docker останавливает создание любых правил iptables. Это самое простое решение, но с самыми серьёзными побочными эффектами.

Что перестанет работать:

  • Контейнеры не смогут выходить в интернет (нет правил masquerading)
  • Связь между контейнерами в разных сетях прекратится
  • Публикация портов (-p) полностью перестанет работать. Все правила пересылки придётся настраивать вручную.

Этот подход подходит только для изолированных контейнеров без доступа к интернету, когда вы настраиваете всю сеть вручную.

Отредактируйте или создайте конфигурацию демона Docker:

sudo nano /etc/docker/daemon.json
{
  "iptables": false
}

Если файл уже содержит данные, добавьте ключ "iptables": false в существующий JSON-объект. Не создавайте второй JSON-объект.

Перезапустите Docker:

sudo systemctl restart docker

Убедитесь, что Docker не создаёт правила iptables:

sudo iptables -L DOCKER -n 2>/dev/null

В Docker 28.x цепочка DOCKER может ещё существовать, но должна содержать только правило DROP вместо обычных правил пересылки. Отсутствие правил ACCEPT означает, что Docker не маршрутизирует трафик к контейнерам.

Проверка с внешнего хоста

Запустите тестовый контейнер снова (он остановился при перезапуске Docker):

docker start ufw-test

С вашей локальной машины:

nmap -p 8080 YOUR_SERVER_IP
PORT     STATE  SERVICE
8080/tcp closed http-proxy

closed (не filtered), потому что Docker больше не создаёт NAT-правила для пересылки трафика. С внешней стороны порт не слушает.

Чтобы откатить, удалите "iptables": false из daemon.json и перезапустите Docker:

sudo systemctl restart docker

Как привязать порты Docker Compose только к localhost?

Для контейнеров за reverse proxy (Nginx, Traefik, Caddy) самый простой вариант — вообще не публиковать порты на 0.0.0.0. Привяжите их к 127.0.0.1. Reverse proxy подключается к контейнеру через localhost. Внешний трафик идёт на reverse proxy через порты 80/443, которые UFW контролирует как обычно.

Не нужно менять iptables, не нужны дополнительные утилиты, работает на любой ОС.

В docker-compose.yml измените привязку портов:

services:
  app:
    image: your-app:latest
    ports:
      - "127.0.0.1:8080:80"
    restart: unless-stopped

  nginx:
    image: nginx:alpine
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
    restart: unless-stopped

Сервис app привязан только к 127.0.0.1:8080. Он недоступен извне сервера. Сервис nginx привязан к 0.0.0.0:80 и 0.0.0.0:443 (по умолчанию, когда IP не указан), которые вы контролируете через UFW.

Для docker run эквивалентный синтаксис:

docker run -d --name app -p 127.0.0.1:8080:80 your-app:latest

Проверка привязки

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

ss -tlnp | grep 8080
LISTEN 0      4096      127.0.0.1:8080      0.0.0.0:*    users:(("docker-proxy",pid=12345,fd=4))

Обратите внимание: адрес прослушивания 127.0.0.1:8080, а не 0.0.0.0:8080. Это значит, что принимаются только локальные соединения.

Проверка с внешнего хоста

С вашей локальной машины:

nmap -p 8080 YOUR_SERVER_IP
PORT     STATE  SERVICE
8080/tcp closed http-proxy

Порт закрыт для внешнего трафика. Reverse proxy обслуживает публичный доступ на портах 80 и 443, которые UFW защищает как обычно.

Это рекомендуемый подход для большинства VPS-конфигураций, где веб-приложения работают за reverse proxy.

Устранение неполадок

Правила исчезают после перезапуска Docker

Если вы использовали подход с цепочкой DOCKER-USER и правила пропадают при перезапуске Docker, проверьте, что ваши правила находятся в /etc/ufw/after.rules, а не добавлены вручную командами iptables. Ручные правила iptables не переживают перезапуск сервисов. Файл after.rules загружается при каждом старте UFW.

sudo ufw reload
sudo iptables -L DOCKER-USER -n -v

Контейнер не может разрешить DNS после применения правил DOCKER-USER

Блок after.rules содержит правило conntrack --ctstate RELATED,ESTABLISHED, которое разрешает DNS-ответам возвращаться в контейнеры. Если DNS-разрешение не работает в контейнерах, проверьте, загружено ли правило conntrack:

sudo iptables -L DOCKER-USER -n -v | grep "RELATED,ESTABLISHED"

Если его нет, проверьте синтаксис блока after.rules и перезагрузите UFW.

Команды ufw-docker завершаются с ошибкой "container not found"

Команда ufw-docker allow требует, чтобы контейнер был запущен. Она разрешает имя контейнера в IP-адрес. Если контейнер остановлен, команда завершится ошибкой. Сначала запустите контейнер, потом добавляйте правило.

Совместимость с nftables на Debian 12

Debian 12 использует nftables как backend файрвола, но UFW и Docker используют уровень совместимости iptables (iptables-nft). Решения в этом руководстве работают одинаково на Debian 12 и Ubuntu 24.04. Проверьте, что используется nft-backend:

sudo iptables --version
iptables v1.8.10 (nf_tables)

Суффикс (nf_tables) подтверждает, что уровень совместимости iptables-nft активен. Все правила цепочки DOCKER-USER работают через этот уровень.

Проверка логов заблокированного трафика

Если соединение неожиданно блокируется после применения исправления цепочки DOCKER-USER, проверьте логи UFW Docker:

sudo journalctl -k | grep "UFW DOCKER BLOCK"

Записи в логе показывают IP источника и порт назначения заблокированных пакетов.

Очистка тестовых ресурсов

Удалите тестовый контейнер, когда закончите:

docker rm -f ufw-test

Если вы добавляли правило UFW deny при тестировании, удалите его:

sudo ufw delete deny 8080

Copyright 2026 Virtua.Cloud. Vse prava zashchishcheny.

Готовы попробовать?

Разверните свой сервер за секунды. Linux, Windows или FreeBSD.

Смотреть тарифы VPS