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.
Правила для established, related и loopback
Правило 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 идёт раньше accept | nft list ruleset, ss -tlnp | Разрешить ct state established,related и tcp dport 22 до policy drop |
| Published port не открывается снаружи | Нет DNAT, конфликт DOCKER-USER или hairpin NAT | nft list table ip nat, nft list chain ip filter DOCKER-USER | Проверить правило Docker, вставить разрешающее правило в DOCKER-USER |
| Контейнер не выходит в интернет | Отключён ip_forward или нет MASQUERADE | sysctl 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-nft | update-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.json | Docker не трогает 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: эта команда снесёт и правила контейнеров.