Docker и nftables: настройка firewall без потери SSH-доступа и с DNAT | AdminWiki

Docker и nftables: настройка firewall без потери SSH-доступа и с DNAT

11 сентября 2026 11 мин. чтения
Содержание статьи

Docker на Linux запускает контейнеры и при старте демон вставляет свои правила в netfilter. На дистрибутивах 2026 года это чаще всего iptables-nft, который транслирует правила в nftables. Поэтому ручной firewall в nftables нужно строить с учётом цепочек Docker, иначе published ports перестанут открываться, а SSH отвалится после первой же смены политики.

Рабочий принцип: свои ограничения для трафика к контейнерам пишите в DOCKER-USER, а не в FORWARD. DNAT для published ports уже делает Docker в table ip nat, chain DOCKER. Вам не нужно дублировать эти правила. Для SSH сначала разрешите ct state established,related и порт 22, только потом ставьте default drop. Применяйте правила из второй SSH-сессии и с автоматическим откатом через at или systemd-таймер.

Ниже разобрана последовательность: как устроен проход пакета, как сделать бэкап и откат, как собрать базовый nftables, как ограничить доступ к published ports, как проверить конфигурацию и что делать при типовых ошибках.

Как Docker встраивается в netfilter и nftables

Docker управляет netfilter через iptables-nft или iptables-legacy. На современных ядрах и дистрибутивах (Ubuntu 22.04+, Debian 12+, Rocky 9+) по умолчанию используется iptables-nft. Демон создаёт собственные цепочки в таблицах filter и nat. Ваши правила в отдельной таблице inet filter сосуществуют с ними, но порядок base chains определяется hook priority. Если поставить policy drop в своей forward-цепочке без явных accept для Docker-бриджей, контейнеры потеряют связь.

Цепочки DOCKER, DOCKER-USER и DOCKER-ISOLATION: зачем они нужны

DOCKER-USER - единственная цепочка в таблице filter, которую Docker не перезаписывает. Сюда добавляют пользовательские ограничения для трафика, который идёт через FORWARD. DOCKER-ISOLATION-STAGE-1 и DOCKER-ISOLATION-STAGE-2 отвечают за изоляцию bridge-сетей: они не дают контейнерам из разных сетей общаться напрямую, если это не разрешено. Цепочка DOCKER в таблице nat содержит DNAT-правила для published ports. Когда вы запускаете контейнер с -p 8080:80, Docker добавляет правило: трафик на порт 8080 хоста перенаправляется на 80 порт контейнера.

Если в daemon.json указать iptables: false, Docker перестаёт создавать эти цепочки. Тогда DNAT, MASQUERADE и фильтрацию FORWARD вы делаете вручную. Этот режим требует полного контроля над netfilter и подходит опытным командам. Для большинства сценариев оставьте управление Docker включённым, а ограничения добавляйте в DOCKER-USER. Подробнее про сетевые драйверы и маршрутизацию: сетевые модели Docker и Kubernetes.

Порядок прохождения пакета: PREROUTING, INPUT, FORWARD, POSTROUTING

Пакет к published port приходит на внешний интерфейс. Он попадает в PREROUTING таблицы nat, где Docker делает DNAT на IP контейнера. Дальше пакет идёт в FORWARD, потому что хост выступает маршрутизатором, и только затем попадает в контейнер. Правило в INPUT на этот трафик не влияет: INPUT обрабатывает пакеты, адресованные самому хосту.

Трафик из контейнера в интернет идёт через FORWARD и POSTROUTING, где Docker добавляет MASQUERADE. MASQUERADE подменяет исходный IP контейнера на IP хоста. Трафик к самому хосту, например SSH на порт 22, проходит через INPUT. Трафик от хоста к published port (hairpin) идёт через OUTPUT и POSTROUTING, а не через PREROUTING. Эти различия объясняют, почему правило в INPUT не открывает контейнер, а правило в FORWARD может не сработать из-за порядка цепочек.

Подготовка: проверка текущего состояния и план отката

Перед изменением правил соберите факты о текущей конфигурации. Проверьте, какой backend использует Docker:

update-alternatives --display iptables
update-alternatives --display ip6tables
docker info | grep -i iptables

Если вывод показывает iptables-nft, ваши правила nftables и правила Docker живут в одном netfilter. Если iptables-legacy, Docker может использовать отдельный backend, и правила nftables не будут видны в iptables-legacy. Это частая причина «правила не работают».

Бэкап текущего ruleset и конфигов Docker

Сохраните текущий ruleset и конфиги. Команды ниже создают файлы, к которым можно вернуться:

nft list ruleset > /root/nft-backup-2026-09-11.nft
cp /etc/nftables.conf /root/nftables.conf.bak
docker network inspect bridge > /root/docker-bridge.json
docker ps --format '{{.Names}} {{.Ports}}' > /root/docker-ports.txt

Откройте вторую SSH-сессию и не закрывайте первую. Если новая сессия не подключается, у вас останется доступ для отката. Для виртуальных машин и LXC сделайте снапшот перед изменениями.

Автоматический откат через at или systemd-таймер

Самый простой способ подстраховаться: запланировать сброс правил через 5 минут. Если вы не отмените задание, доступ восстановится автоматически.

echo 'nft flush ruleset' | at now + 5 minutes

После успешной проверки удалите задание командой atrm. Если at не установлен, используйте transient systemd-юнит:

systemd-run --on-active=5min --unit=nft-rollback /usr/sbin/nft flush ruleset

Для отмены выполните systemctl stop nft-rollback.timer и systemctl reset-failed nft-rollback.service. Не применяйте nft flush ruleset на хосте с работающим Docker без плана восстановления: эта команда удалит и правила Docker.

Базовая конфигурация nftables: INPUT, established и SSH

Базовая таблица inet filter закрывает доступ к хосту, но не ломает уже установленные соединения. Политику input ставьте drop только после правил для established,related, loopback и SSH.

Правило ct state established,related accept должно идти первым в input. Без него ответные пакеты для уже открытых SSH-сессий будут отброшены, и вы потеряете доступ. Правило iif lo accept разрешает локальные сервисы, которые ходят через loopback.

Разрешение SSH и защита от блокировки

Разрешите SSH до политики drop. Если SSH работает на нестандартном порту, укажите его в правиле. Для антибрутфорса можно добавить limit rate, но не меняйте порт и правила одновременно. Сначала проверьте новую сессию, потом закрывайте старую. Пример безопасного каркаса:

table inet filter {
  chain input {
    type filter hook input priority 0; policy drop;
    ct state established,related accept
    iif lo accept
    tcp dport 22 accept
    icmp type echo-request accept
    icmpv6 type { nd-neighbor-solicit, nd-router-advert, echo-request } accept
  }
}

Дополнительные меры по SSH и доступу к серверу разобраны в статье политика безопасности Linux-сервера. Если используете rate limit, проверьте, что он не блокирует вашу вторую сессию: tcp dport 22 ct state new limit rate 10/minute accept.

DNAT для published ports Docker: как не сломать проброс

Docker сам добавляет DNAT для каждого published port. Ваша задача: не создать конфликт и ограничить доступ по source IP там, где это нужно.

Где Docker вставляет DNAT-правила и как их увидеть

Правила DNAT находятся в table ip nat, chain DOCKER. Посмотреть их можно командами:

nft list table ip nat
iptables -t nat -L DOCKER -n -v
docker network inspect bridge

Если вы используете nftables напрямую, не дублируйте DNAT в своей таблице. Docker уже делает это при старте контейнера. Дублирование приводит к конфликту: пакет может уйти не туда или получить двойной NAT.

Ограничение доступа к published ports через DOCKER-USER

Все ограничения для трафика к контейнерам добавляйте в DOCKER-USER. Эта цепочка вызывается из FORWARD до правил Docker, которые принимают трафик. Вставляйте правило в начало цепочки, чтобы оно сработало раньше return:

nft insert rule ip filter DOCKER-USER iifname "eth0" ip saddr != 10.0.0.0/8 tcp dport 8080 drop

Это правило запрещает доступ к published port 8080 с внешнего интерфейса eth0 для всех, кроме сети 10.0.0.0/8. Если нужно разрешить конкретные адреса, замените условие на ip saddr { 203.0.113.10, 198.51.100.0/24 } accept. Помните: DOCKER-USER обрабатывает только форвардимый трафик. Трафик к самому хосту идёт через INPUT.

Hairpin NAT и доступ к контейнеру с самого хоста

Запрос с хоста на published port не проходит через PREROUTING. Пакет идёт через OUTPUT и POSTROUTING. Docker добавляет отдельные правила для hairpin, но если вы отключили iptables в Docker или изменили nat-таблицу, доступ с хоста может не работать. Проверяйте hairpin отдельно: curl http://127.0.0.1:8080 и curl http://IP_ХОСТА:8080.

Готовый конфиг nftables для хоста с Docker

Этот конфиг создаёт отдельную таблицу inet filter и не трогает таблицы Docker. Политика forward оставлена drop, но добавлены accept для established и трафика через docker0. Если у вас есть пользовательские bridge-сети, добавьте их интерфейсы (br-...) в правила forward.

Разбор файла построчно

add table inet filter
flush table inet filter

table inet filter {
  chain input {
    type filter hook input priority 0; policy drop;
    ct state established,related accept
    iif lo accept
    tcp dport 22 accept
    icmp type echo-request accept
  }

  chain forward {
    type filter hook forward priority 0; policy drop;
    ct state established,related accept
    iifname "docker0" accept
    oifname "docker0" accept
  }

  chain output {
    type filter hook output priority 0; policy accept;
  }
}

add table создаёт таблицу, если её нет. flush table inet filter очищает только вашу таблицу. Блок table inet filter описывает цепочки input, forward и output. В input разрешены established, loopback, SSH и ICMP. В forward разрешены established и трафик Docker. В output политика accept: хост может инициировать исходящие соединения.

Применение и проверка

Сначала проверьте синтаксис, затем примените правила и проверьте доступ:

nft -c -f /etc/nftables.conf
nft -f /etc/nftables.conf
systemctl enable --now nftables
nft list ruleset
ss -tlnp | grep :22
docker ps --format '{{.Names}} {{.Ports}}'

Проверьте новую SSH-сессию до закрытия старой. Проверьте published port с внешнего адреса и с самого хоста. Проверьте исходящий трафик из контейнера: docker exec -it ИМЯ_КОНТЕЙНЕРА ping -c 2 1.1.1.1. Если что-то не работает, не паникуйте: у вас есть вторая сессия и запланированный откат.

Диагностика типовых ошибок

Ниже таблица симптомов и команд для быстрой проверки. Начните с nft list ruleset и conntrack -L.

СимптомВероятная причинаЧто проверитьИсправление
SSH не подключаетсяПорт 22 закрыт или drop идёт раньше acceptnft list ruleset, ss -tlnpРазрешить ct state established,related и tcp dport 22 до policy drop
Published port не открывается снаружиНет DNAT, конфликт DOCKER-USER или hairpin NATnft list table ip nat, nft list chain ip filter DOCKER-USERПроверить правило Docker, вставить разрешающее правило в DOCKER-USER
Контейнер не выходит в интернетОтключён ip_forward или нет MASQUERADEsysctl net.ipv4.ip_forward, nft list table ip natВключить ip_forward, проверить MASQUERADE в POSTROUTING
Правила в FORWARD не срабатываютDocker вставляет свои правила раньшеnft list chain ip filter FORWARDПеренести ограничения в DOCKER-USER
Правила не видны в iptablesКонфликт iptables-legacy и iptables-nftupdate-alternatives --display iptablesПереключить backend и перезапустить Docker

SSH не подключается после применения правил

Проверьте порядок правил в input. Самая частая ошибка: policy drop стоит до ct state established,related accept. Вторая причина: SSH слушает не 22 порт, а правило разрешает только 22. Третья: fail2ban или другой сервис блокирует ваш IP. Команды для разбора:

nft list ruleset
conntrack -L | grep :22
tcpdump -i any port 22
journalctl -u nftables
journalctl -u ssh

Если tcpdump видит SYN, но ответа нет, пакет дропается в INPUT. Если SYN не виден, проблема на стороне сети или провайдера. Пошаговая диагностика сетевых сбоев Docker и Kubernetes собрана в статье диагностика сетевых проблем в Docker и Kubernetes.

Published port не открывается снаружи

Сначала проверьте, что контейнер слушает порт и Docker создал DNAT. Затем проверьте DOCKER-USER: ваше правило drop могло сработать раньше разрешающего. Если порт не открывается с самого хоста, проверьте hairpin NAT. Команды:

nft list table ip nat
iptables -t nat -L DOCKER -n -v
nft list chain ip filter DOCKER-USER
docker network inspect bridge
curl -v http://127.0.0.1:8080

Контейнер не выходит в интернет

Проверьте ip_forward. Без него ядро не маршрутизирует пакеты между интерфейсами. Затем проверьте MASQUERADE в POSTROUTING и правила FORWARD. Если вы поставили policy drop в своей forward-цепочке, добавьте accept для docker0 и br-интерфейсов. Команды:

sysctl net.ipv4.ip_forward
nft list chain ip filter FORWARD
nft list table ip nat
docker run --rm alpine ping -c 2 1.1.1.1

Конфликт iptables-legacy и iptables-nft

Docker может использовать один backend, а вы смотрите правила в другом. Проверьте альтернативы и перезапустите Docker после переключения:

update-alternatives --display iptables
update-alternatives --display ip6tables
systemctl restart docker

Ещё одна причина «правила не работают»: firewalld или ufw управляют тем же netfilter. Их правила могут срабатывать раньше или позже ваших. Не смешивайте firewalld и ручной nftables без понимания порядка. Практические примеры маршрутизации и NAT в Linux разобраны в статье Маршрутизация в Linux 2026: iptables, nftables и firewalld.

Сравнение подходов и рекомендации для продакшена

Выбор зависит от того, кто управляет netfilter: вы, Docker или менеджер firewall. Ниже сравнение.

ПодходПлюсыМинусыКогда выбирать
nftables + DOCKER-USERПолный контроль, совместимость с Docker, минимум конфликтовНужно понимать цепочки и приоритетыПродакшен на Ubuntu, Debian, Rocky 9+
iptables-nftПривычный синтаксис, Docker использует тот же backendТрансляция в nftables, меньше возможностейМиграция со старых скриптов
firewalld с DockerЗоны и сервисы, удобно для хостовых сервисовТребует ручной интеграции с Docker, правила могут конфликтоватьЕсли firewalld уже стандарт в компании
iptables: false в daemon.jsonDocker не трогает netfilterНужно вручную делать DNAT, MASQUERADE, FORWARDТолько для команд с глубокой экспертизой

Когда стоит отключать iptables в Docker

Отключение iptables в Docker оправдано, если вы строите собственный сетевой слой, используете CNI или полностью управляете nftables. В этом случае Docker не создаёт DNAT и MASQUERADE, и published ports работают через docker-proxy или вообще не работают. Пример daemon.json:

{
  "iptables": false
}

После изменения перезапустите Docker: systemctl restart docker. Проверьте, что контейнеры ходят в сеть и published ports доступны. Для тестового стенда с Docker и nftables удобно поднять отдельный VPS: Timeweb Cloud предоставляет облачные серверы и Kubernetes, где можно безопасно проверить правила на изолированном хосте.

Итог и чек-лист безопасной настройки

Перед применением правил в продакшене пройдите чек-лист. Он снижает риск потерять SSH и доступ к контейнерам.

  • Сохраните nft list ruleset, /etc/nftables.conf и вывод docker network inspect.
  • Откройте вторую SSH-сессию и не закрывайте первую до проверки.
  • Запланируйте откат через at now + 5 minutes или systemd-run --on-active=5min.
  • Проверьте backend: update-alternatives --display iptables.
  • В input разрешите ct state established,related, loopback и SSH до policy drop.
  • Ограничения для контейнеров добавляйте в DOCKER-USER, не в FORWARD.
  • Не дублируйте DNAT: Docker уже создаёт правила в table ip nat, chain DOCKER.
  • Проверьте published port снаружи и с хоста, учитывайте hairpin NAT.
  • Проверьте исходящий трафик контейнера: ping 1.1.1.1 и DNS.
  • Включите nftables.service и проверьте nft list ruleset после перезагрузки.

Если после применения правил что-то не работает, вернитесь к бэкапу: nft -f /root/nft-backup-2026-09-11.nft. На хосте с Docker не выполняйте nft flush ruleset без отключения Docker: эта команда снесёт и правила контейнеров.

Поделиться:
Сохранить гайд? В закладки браузера