Настройка проброса портов (port forwarding) с помощью DNAT в nftables: практическое руководство | AdminWiki

Настройка проброса портов (port forwarding) с помощью DNAT в nftables: практическое руководство

22 июля 2026 12 мин. чтения

Destination NAT (DNAT) в nftables - это механизм изменения адреса назначения входящего IP-пакета до того, как ядро примет решение о его маршрутизации. На практике это позволяет перенаправить трафик, пришедший на публичный IP вашего шлюза, на внутренний сервер, контейнер или виртуальную машину, которые не имеют прямого выхода в интернет. В этой статье вы получите готовые, проверенные конфигурации для проброса SSH, HTTP и HTTPS, настроите корректный возврат ответного трафика через SNAT или masquerading и обеспечите сохранность правил после перезагрузки.

Мы разберем полный путь пакета: от попадания на сетевой интерфейс до перенаправления в изолированную среду. Вы узнаете, почему правила DNAT размещаются строго в цепочке prerouting таблицы nat, как conntrack отслеживает состояния соединений и что делать, если проброшенный сервис не отвечает. Все примеры протестированы на актуальных версиях ядра Linux и nftables, что исключает ситуацию «у вас не заработало, потому что версия не та».

Если вы мигрируете с iptables, вы найдете здесь прямое сопоставление синтаксиса. Если вы строите шлюз с нуля - получите полный листинг /etc/nftables.conf, который можно адаптировать под свою инфраструктуру за несколько минут. Для более широкого контекста по управлению трафиком в современных дистрибутивах обратитесь к руководству по настройке политик маршрутизации в Linux, где детально сравниваются iptables, nftables и firewalld.

Что такое DNAT и как он работает в nftables

DNAT (Destination Network Address Translation) подменяет адрес назначения в заголовке IP-пакета. Когда внешний клиент отправляет запрос на публичный IP вашего маршрутизатора, правило DNAT заменяет этот публичный адрес на приватный адрес внутреннего хоста. Ядро затем перенаправляет пакет в соответствии с таблицей маршрутизации. Без DNAT пакет был бы обработан локально самим шлюзом или отброшен, поскольку сервис на нем не слушает этот порт.

В отличие от SNAT, который изменяет адрес источника для исходящих пакетов, DNAT работает с входящим трафиком. Это фундаментальное различие определяет, в какой цепочке и на каком хуке размещаются правила. Если вы ранее работали с iptables, логика осталась прежней, но синтаксис стал чище: вместо iptables -t nat -A PREROUTING вы используете nft add rule nat prerouting. Подробный разбор взаимодействия сетевого экрана и таблицы маршрутизации, включая порядок прохождения пакета через цепочки PREROUTING, FORWARD и POSTROUTING, вы найдете в материале о пути пакета в Linux.

Место DNAT в архитектуре nftables: таблицы, цепочки и хуки

Архитектура nftables строится на трех уровнях: таблицы, цепочки и правила. Таблица определяет семейство протоколов (ip, ip6, inet) и тип обрабатываемого трафика. Для DNAT используется таблица семейства ip или inet с типом nat. Внутри таблицы создается цепочка, которая привязывается к конкретному хуку netfilter - точке в стеке ядра, через которую проходят пакеты.

Хук prerouting срабатывает до принятия решения о маршрутизации. Именно здесь ядро еще не знает, предназначен ли пакет локальному процессу или должен быть перенаправлен дальше. Размещение DNAT на prerouting гарантирует, что подмена адреса назначения произойдет до маршрутизации, и ядро примет решение уже на основе нового, измененного адреса. Если бы DNAT применялся на хуке input или forward, пакет уже был бы классифицирован, и изменение адреса не привело бы к желаемой маршрутизации.

Схема прохождения пакета выглядит так: пакет попадает на интерфейс → хук prerouting (здесь срабатывает DNAT) → решение о маршрутизации → если пакет предназначен для другого хоста, он уходит на хук forward → хук postrouting (здесь применяется SNAT или masquerade) → пакет покидает интерфейс. Эта последовательность критична для понимания: DNAT и SNAT работают на разных этапах обработки одного и того же пакета.

Подготовка системы: включаем IP forwarding

Параметр net.ipv4.ip_forward управляет способностью ядра Linux маршрутизировать пакеты между сетевыми интерфейсами. По умолчанию он выключен, и хост работает как конечная станция, а не как маршрутизатор. Если вы настраиваете проброс портов на шлюзе, этот параметр должен быть включен.

Проверьте текущее состояние:

sysctl net.ipv4.ip_forward

Если вывод показывает net.ipv4.ip_forward = 0, включите его немедленно:

sysctl -w net.ipv4.ip_forward=1

Эта команда применяет изменение сразу, но оно сбросится после перезагрузки. Для постоянного применения создайте файл /etc/sysctl.d/99-forward.conf или добавьте строку в /etc/sysctl.conf:

echo "net.ipv4.ip_forward = 1" > /etc/sysctl.d/99-forward.conf
sysctl -p /etc/sysctl.d/99-forward.conf

Без включенного forwarding пакеты, пришедшие на внешний интерфейс и перенаправленные правилом DNAT на внутренний хост, будут отброшены ядром на этапе маршрутизации. Это самая частая причина неработающего проброса, которую мы исключаем первым шагом.

Базовый синтаксис правил DNAT в nftables

Правило DNAT добавляется в цепочку prerouting таблицы nat. Общий синтаксис:

nft add rule nat prerouting [фильтр] dnat to [адрес:порт]

Фильтр определяет, какие пакеты подлежат трансляции. Вы можете указать протокол (tcp, udp), порт назначения (dport), IP-адрес источника (saddr) или интерфейс (iif). Если фильтр опущен, правило применится ко всем пакетам, что редко требуется на практике.

Рассмотрим пример проброса TCP-порта 8080 на внутренний хост 192.168.1.100 с изменением порта на 80:

nft add rule nat prerouting tcp dport 8080 dnat to 192.168.1.100:80

Здесь внешний клиент подключается к порту 8080 шлюза, а внутренний веб-сервер получает запрос на свой штатный порт 80. Прозрачная подмена порта упрощает жизнь: вы можете публиковать несколько веб-серверов на разных внешних портах, не трогая их внутреннюю конфигурацию.

Проброс конкретного порта: SSH (порт 22)

Проброс SSH - самый востребованный сценарий. Допустим, у вас есть внутренний сервер 192.168.1.10, на который нужно попасть извне через публичный IP шлюза. Правило выглядит так:

nft add rule nat prerouting tcp dport 22 dnat to 192.168.1.10:22

Разбор элементов правила:

  • nat prerouting - цепочка в таблице nat на хуке prerouting
  • tcp - фильтр по протоколу (SSH работает поверх TCP)
  • dport 22 - фильтр по порту назначения
  • dnat to 192.168.1.10:22 - заменить адрес назначения на 192.168.1.10, порт оставить 22

Если на шлюзе уже работает собственный SSH-сервер на порту 22, вы можете пробросить внешний порт 2222 на внутренний порт 22:

nft add rule nat prerouting tcp dport 2222 dnat to 192.168.1.10:22

Проброс HTTP и HTTPS: порты 80 и 443

Для публикации веб-сервера за шлюзом добавьте два правила:

nft add rule nat prerouting tcp dport 80 dnat to 192.168.1.100:80
nft add rule nat prerouting tcp dport 443 dnat to 192.168.1.100:443

nftables поддерживает множественный dport, что позволяет объединить оба проброса в одно правило:

nft add rule nat prerouting tcp dport { 80, 443 } dnat to 192.168.1.100

Обратите внимание: при использовании множественного dport вы не указываете порт в цели DNAT - трафик будет перенаправлен на те же порты, что и в запросе. Это удобно, когда внутренний сервер слушает стандартные порты.

Обработка обратного трафика: SNAT и masquerading

DNAT решает только половину задачи. Когда внутренний сервер 192.168.1.100 отправляет ответ внешнему клиенту, исходный адрес пакета - 192.168.1.100. Внешний клиент ожидает ответ от публичного IP шлюза и отбросит пакет с приватным адресом источника. Возникает асимметричная маршрутизация: запрос пришел через шлюз, а ответ пытается уйти напрямую.

Решение - Source NAT (SNAT) или masquerading на хуке postrouting. Оба механизма подменяют адрес источника исходящего пакета на адрес внешнего интерфейса шлюза. Разница в способе указания адреса:

  • SNAT - вы жестко задаете IP-адрес. Используйте, когда у шлюза статический публичный IP.
  • Masquerade - адрес определяется автоматически по IP внешнего интерфейса. Применяйте при динамическом IP (DHCP от провайдера) или если не хотите привязываться к конкретному адресу.

Правило masquerade для типового шлюза с интерфейсом eth0 в качестве внешнего:

nft add rule nat postrouting oif eth0 masquerade

Это правило применяется ко всем пакетам, покидающим внешний интерфейс, и подменяет их исходный адрес на IP этого интерфейса. Если у вас несколько внутренних подсетей, правило останется тем же - masquerade отработает для всех.

Для статического IP используйте SNAT:

nft add rule nat postrouting oif eth0 snat to 203.0.113.10

Где 203.0.113.10 - ваш публичный IP. SNAT работает быстрее masquerade, поскольку не выполняет поиск адреса интерфейса для каждого пакета, но требует обновления правила при смене IP.

Отслеживание соединений с conntrack

Connection tracking (conntrack) - подсистема ядра, которая хранит информацию о каждом сетевом соединении: адреса, порты, состояние и связанные соединения. Для NAT conntrack критически важен: он позволяет ядру сопоставить ответный пакет с исходным запросом и автоматически применить обратную трансляцию адресов.

Когда пакет проходит через правило DNAT, conntrack создает запись, фиксирующую факт трансляции. Ответный пакет от внутреннего сервера автоматически подвергается обратному DNAT (то есть SNAT для исходного адреса) на основе этой записи. Вам не нужно создавать отдельные правила для обратного трафика - conntrack делает это сам.

Для фильтрации трафика на основе состояний соединений nftables предоставляет выражения ct state. Рекомендуемая практика - разрешить в цепочке filter forward только трафик с состояниями established и related, а новые соединения разрешать только к проброшенным сервисам:

nft add rule inet filter forward ct state established,related accept
nft add rule inet filter forward ct state new tcp dport 22 accept
nft add rule inet filter forward ct state new tcp dport { 80, 443 } accept
nft add rule inet filter forward drop

Проверить текущую таблицу conntrack можно командой:

conntrack -L

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

Сохранение правил и автозагрузка при старте системы

Правила, добавленные через nft add rule, действуют немедленно, но существуют только в памяти ядра. После перезагрузки они исчезнут. Чтобы конфигурация пережила ребут, сохраните текущий набор правил в файл:

nft list ruleset > /etc/nftables.conf

Проверьте содержимое файла - он должен содержать полное описание всех таблиц и цепочек в читаемом синтаксисе. Теперь включите и запустите службу nftables:

systemctl enable nftables
systemctl start nftables

Служба при старте загрузит правила из /etc/nftables.conf. Убедитесь, что загрузка прошла без ошибок:

systemctl status nftables
nft list ruleset

Атомарное применение правил - еще одно преимущество nftables перед iptables. Вместо последовательного добавления правил вы можете загрузить всю конфигурацию одной командой nft -f /etc/nftables.conf, что исключает промежуточные состояния с частично примененными правилами.

Проброс портов до контейнеров и виртуальных машин

Проброс портов до изолированных сред - контейнеров Docker, LXC или виртуальных машин KVM - имеет свою специфику. Эти среды часто используют bridge-интерфейсы и пользовательские цепочки nftables, созданные автоматически.

Docker, например, при запуске контейнера с флагом -p 8080:80 создает собственные правила DNAT в таблице nat и правила фильтрации в цепочке DOCKER. Если вы настраиваете DNAT вручную на уровне шлюза для доступа к контейнеру извне, конфликта с правилами Docker не возникнет - ваш шлюз и хост с контейнерами - это разные машины.

Пример проброса порта 8080 с публичного IP шлюза на контейнер, работающий на хосте 192.168.1.50:

nft add rule nat prerouting tcp dport 8080 dnat to 192.168.1.50:8080

Убедитесь, что на хосте 192.168.1.50 маршрут по умолчанию указывает на ваш шлюз, иначе ответный трафик пойдет другим путем и conntrack не сможет корректно отследить соединение. Для виртуальных машин KVM, подключенных через bridge, логика та же: DNAT на шлюзе указывает на IP виртуальной машины в bridge-сети, а SNAT или masquerade на шлюзе обеспечивает корректный возврат трафика.

Диагностика и отладка правил DNAT

Когда проброс не работает, методичная диагностика экономит часы. Начните с проверки прохождения пакета на каждом этапе.

Включите мониторинг событий nftables в реальном времени:

nft monitor

Эта команда показывает все пакеты, попадающие под правила, и позволяет увидеть, срабатывает ли ваш DNAT. Для более детального анализа добавьте счетчики к правилам:

nft add rule nat prerouting tcp dport 22 counter dnat to 192.168.1.10:22

Просмотр счетчиков:

nft list ruleset -a

Ненулевой счетчик подтверждает, что пакеты доходят до правила. Если счетчик нулевой, проблема на уровне сети - проверьте, доходят ли пакеты до интерфейса шлюза с помощью tcpdump:

tcpdump -i eth0 port 22

Замените eth0 на ваш внешний интерфейс. Если пакеты видны в tcpdump, но счетчик правила не растет, проверьте порядок правил и наличие других таблиц, которые могут перехватывать трафик раньше.

Для логирования конкретных пакетов добавьте правило с log перед DNAT:

nft add rule nat prerouting tcp dport 22 log prefix "DNAT-SSH: " dnat to 192.168.1.10:22

Логи появятся в системном журнале. Типичные ошибки: не включен ip_forward, отсутствует masquerade для обратного трафика, внутренний сервер не слушает указанный порт или его маршрут по умолчанию указывает не на шлюз. Проверьте каждый пункт последовательно.

Полный пример конфигурации для шлюза

Ниже приведен готовый файл /etc/nftables.conf, который реализует проброс SSH и HTTP/HTTPS, masquerade для внутренней сети и базовую фильтрацию трафика с использованием conntrack. Скопируйте его, замените адреса и интерфейсы на свои и загрузите командой nft -f /etc/nftables.conf.

#!/usr/sbin/nft -f

# Очистка текущих правил
flush ruleset

# Таблица nat для трансляции адресов
table inet nat {
    # Цепочка для DNAT - проброс портов с внешнего IP
    chain prerouting {
        type nat hook prerouting priority dstnat; policy accept;
        
        # Проброс SSH на внутренний сервер
        tcp dport 22 dnat to 192.168.1.10:22
        
        # Проброс HTTP и HTTPS на веб-сервер
        tcp dport { 80, 443 } dnat to 192.168.1.100
    }

    # Цепочка для SNAT/masquerade - обратный трафик
    chain postrouting {
        type nat hook postrouting priority srcnat; policy accept;
        
        # Masquerade для всех пакетов, уходящих через внешний интерфейс
        oif "eth0" masquerade
    }
}

# Таблица filter для контроля доступа
table inet filter {
    # Цепочка для входящего трафика на сам шлюз
    chain input {
        type filter hook input priority filter; policy drop;
        
        # Разрешить loopback
        iif lo accept
        
        # Разрешить established и related соединения
        ct state established,related accept
        
        # Разрешить SSH для управления шлюзом (опционально)
        tcp dport 22 ct state new accept
        
        # Разрешить ICMP (ping)
        ip protocol icmp accept
    }

    # Цепочка для транзитного трафика
    chain forward {
        type filter hook forward priority filter; policy drop;
        
        # Разрешить established и related
        ct state established,related accept
        
        # Разрешить новые соединения к проброшенным сервисам
        ct state new tcp dport 22 accept
        ct state new tcp dport { 80, 443 } accept
    }

    # Цепочка для исходящего трафика
    chain output {
        type filter hook output priority filter; policy accept;
    }
}

Адаптируйте конфигурацию под свою среду: замените eth0 на имя вашего внешнего интерфейса, скорректируйте внутренние IP-адреса и набор пробрасываемых портов. После загрузки правил проверьте их применение и убедитесь, что счетчики растут при обращении к сервисам извне. Для аудита получившейся конфигурации и проверки открытых портов используйте методику из руководства по аудиту сетевого периметра - это поможет выявить избыточные разрешения и ошибки NAT.

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