Задача и контекст
SNAT на VDS — это не только про «интернет для контейнеров». Это про предсказуемые исходящие IP, строгий контроль того, куда могут ходить ваши сервисы, и про стабильность под нагрузкой. Миграция на nftables ускоряет фильтрацию и упорядочивает правила, а правильный выбор между masquerade и явным snat to экономит порты и нервы. В статье разберём базовую топологию, устойчивую схему цепочек, нюансы с несколькими публичными адресами и аккуратный контроль исходящего трафика.
Во многих сценариях это востребовано на VDS с несколькими публичными IP, когда нужно жёстко закрепить исходящий адрес для отдельных сервисов или контейнеров.
Минимальная топология и термины
Типичный кейс: на VDS один физический интерфейс, например eth0, с одним или несколькими публичными IP. Внутри — мост br0 с подсетью, например 10.10.0.0/24, к которому подключены контейнеры/виртуалки. Внешний мир видит только eth0; локальные узлы уходят в интернет через NAT.
Базовые определения:
- SNAT — подмена исходного IP/порта для исходящих пакетов.
- MASQUERADE — разновидность SNAT, автоматически берёт адрес интерфейса (удобно при динамическом IP).
- conntrack — отслеживание состояний соединений, обратный трафик «распаковывается» автоматически.
- policy routing — выбор таблицы маршрутизации по правилам (
ip rule), обычно на основе меток.
Подготовка системы
Включаем форвардинг и настраиваем sysctl
Без форвардинга NAT не заработает. Включаем IPv4 и, при необходимости, IPv6:
sysctl -w net.ipv4.ip_forward=1
sysctl -w net.ipv6.conf.all.forwarding=1
Фиксируем перманентно:
cat > /etc/sysctl.d/99-nat.conf << 'EOF'
net.ipv4.ip_forward = 1
net.ipv6.conf.all.forwarding = 1
# Умеренный rp_filter, чтобы не ломать асимметрию
net.ipv4.conf.all.rp_filter = 2
net.ipv4.conf.default.rp_filter = 2
EOF
sysctl --system
Если у провайдера включены жёсткие антиспуфинг-фильтры, убедитесь, что используете только выделенные вам публичные адреса. SNAT на «чужие» IP работать не будет.
Базовая структура nftables
Держать всё в одном файле удобно и прозрачно. Создадим минимальные таблицы: inet filter для политики и ip nat для NAT.
nft add table inet filter
nft 'add chain inet filter input { type filter hook input priority 0; policy drop; }'
nft 'add chain inet filter forward { type filter hook forward priority 0; policy drop; }'
nft 'add chain inet filter output { type filter hook output priority 0; policy accept; }'
# Базовая гигиена: established/related, локальный луп и SSH
nft add rule inet filter input ct state established,related accept
nft add rule inet filter input iifname "lo" accept
nft add rule inet filter input tcp dport 22 ct state new accept
# FORWARD: по умолчанию закрыто, откроем выборочно ниже
nft add table ip nat
nft 'add chain ip nat prerouting { type nat hook prerouting priority -100; }'
nft 'add chain ip nat postrouting { type nat hook postrouting priority 100; }'
Такой каркас даёт нам строгую политику по умолчанию, но при этом не мешает локальным исходящим процессам на самом VDS.

MASQUERADE для одного публичного IP
Простейший случай — один публичный IP на eth0. Маскарадинг берёт адрес автоматически, подойдёт, если IP может меняться (реже — на VDS, чаще — на домашних узлах).
# Разрешаем форвард из приватной зоны в интернет по нужным протоколам
nft add rule inet filter forward ct state established,related accept
nft add rule inet filter forward iifname "br0" oifname "eth0" tcp dport { 80,443 } accept
nft add rule inet filter forward iifname "br0" oifname "eth0" udp dport 53 accept
nft add rule inet filter forward iifname "br0" oifname "eth0" icmp type echo-request accept
# Маскарадинг всего трафика из подсети 10.10.0.0/24
nft add rule ip nat postrouting oifname "eth0" ip saddr 10.10.0.0/24 masquerade
Если требуется тонкая настройка по портам для экономии эфемерных портов, можно ограничить диапазон и добавить рандомизацию:
nft add rule ip nat postrouting oifname "eth0" ip saddr 10.10.0.0/24 meta l4proto { tcp, udp } masquerade to :1024-65535 random
masquerade удобен простотой, но при статическом адресе и высокой нагрузке явный snat to обычно производительнее.
SNAT на конкретный адрес при нескольких публичных IP
Частый запрос: есть несколько публичных адресов на одном интерфейсе, например 203.0.113.10/32 и 203.0.113.11/32. Нужно, чтобы конкретные внутренние хосты выходили в интернет каждый со «своего» IP. Здесь лучше использовать явный snat to, чтобы не полагаться на «адрес интерфейса».
Привязка по источнику
# Один внутренний хост - один внешний IP
nft add rule ip nat postrouting ip saddr 10.10.0.10 oifname "eth0" snat to 203.0.113.10
nft add rule ip nat postrouting ip saddr 10.10.0.11 oifname "eth0" snat to 203.0.113.11
# Остальное - общий маскарадинг (если нужен «дефолт»)
nft add rule ip nat postrouting oifname "eth0" ip saddr 10.10.0.0/24 masquerade
Порядок правил важен: более специфичные snat — выше, затем общий masquerade или общее правило snat для подсети.
Привязка по сервису/порту
Иногда один и тот же внутренний хост должен выходить под разными исходными IP в зависимости от назначения. Это удобно, когда сторонние API через список доверенных адресов привязываются к IP.
# HTTP(S) - через 203.0.113.10
nft add rule ip nat postrouting ip saddr 10.10.0.20 tcp dport { 80,443 } oifname "eth0" snat to 203.0.113.10
# Остальное - через 203.0.113.11
nft add rule ip nat postrouting ip saddr 10.10.0.20 oifname "eth0" snat to 203.0.113.11
При большом количестве правил полезно вынести подсети/адреса в sets и делать матчи через них — так проще сопровождать.
Пулы портов и устойчивость
Для большого числа исходящих соединений добавляйте флаги random и persistent, а также ограничивайте диапазон портов, чтобы упростить отладку:
nft add rule ip nat postrouting ip saddr 10.10.0.10 oifname "eth0" tcp dport { 80,443 } snat to 203.0.113.10:20000-29999 random,persistent
Флаг persistent помогает стабилизировать выбор исходного порта для одного и того же потока и уменьшить churn в conntrack.
Контроль исходящего трафика: белые списки и запреты
SNAT решает «кто мы» снаружи, а фильтр — «куда нам можно». На VDS правильнее применить стратегию deny-by-default на цепочке forward и открывать только нужные направления. Это не только безопасность, но и страховка от случайных утечек и спама.
# Базовые состояния
nft add rule inet filter forward ct state established,related accept
# Разрешаем только web-выход наружу
nft add rule inet filter forward iifname "br0" oifname "eth0" tcp dport { 80,443 } accept
# Разрешаем DNS только к своему резолверу (пример адресов-заглушек)
nft add rule inet filter forward iifname "br0" oifname "eth0" udp dport 53 ip daddr { 192.0.2.53 } accept
# ICMP для диагностики
nft add rule inet filter forward iifname "br0" oifname "eth0" icmp type { echo-request, time-exceeded, destination-unreachable } accept
# Почтовый порт закрыт всем, кроме одного узла
nft add rule inet filter forward ip saddr 10.10.0.25 tcp dport 25 accept
nft add rule inet filter forward tcp dport 25 drop
# Остальное логируем и дропаем
nft add rule inet filter forward counter log prefix "DROP-OUT " flags all drop
Такой набор правил радикально сокращает поверхность атак, убирает случайные исходящие SMTP-попытки и дисциплинирует зависимости сервисов, вынуждая их явно использовать прокси и разрешённые направления.
Policy routing и fwmark: когда одного шлюза мало
В мире VDS чаще всего один аплинк и одна таблица маршрутизации. Но если у вас несколько интерфейсов или отдельные next-hop под разные публичные IP, понадобится policy routing. Идея такая: маркируем исходящий трафик (по пользователю, группе, источнику), далее ip rule отправляет помеченные пакеты в нужную таблицу с собственным default.
# Цепочки для маркировки
nft add table inet mangle
nft 'add chain inet mangle prerouting { type filter hook prerouting priority -150; }'
nft 'add chain inet mangle output { type route hook output priority -150; }'
# Пример: весь трафик от узла 10.10.0.30 помечаем 100
nft add rule inet mangle prerouting ip saddr 10.10.0.30 meta mark set 100
# Локальные процессы пользователя backup - метка 200
nft add rule inet mangle output meta skuid "backup" meta mark set 200
# Правила маршрутизации по меткам
ip rule add fwmark 100 table 100
ip rule add fwmark 200 table 200
# Таблицы маршрутизации (пример для второго интерфейса/шлюза)
ip route add default via 198.51.100.1 dev eth1 table 100
ip route add default via 203.0.113.1 dev eth0 table 200
Помните: policy routing решает выбор маршрута, а SNAT определяет исходный IP. Часто нужно и то, и другое: меткой уводим поток через нужный шлюз, а в postrouting явно задаём snat to соответствующему публичному адресу.

Персистентность конфигурации
На большинстве дистрибутивов nftables можно грузить из /etc/nftables.conf. Удобно держать всё как единую декларацию:
cat > /etc/nftables.conf << 'EOF'
flush ruleset
table inet filter {
chain input {
type filter hook input priority 0;
policy drop;
ct state established,related accept
iifname "lo" accept
tcp dport 22 ct state new accept
}
chain forward {
type filter hook forward priority 0;
policy drop;
ct state established,related accept
iifname "br0" oifname "eth0" tcp dport { 80,443 } accept
iifname "br0" oifname "eth0" udp dport 53 ip daddr { 192.0.2.53 } accept
iifname "br0" oifname "eth0" icmp type { echo-request, time-exceeded, destination-unreachable } accept
ip saddr 10.10.0.25 tcp dport 25 accept
tcp dport 25 drop
counter log prefix "DROP-OUT " flags all drop
}
chain output {
type route hook output priority 0;
policy accept;
}
}
table ip nat {
chain prerouting {
type nat hook prerouting priority -100;
}
chain postrouting {
type nat hook postrouting priority 100;
ip saddr 10.10.0.10 oifname "eth0" snat to 203.0.113.10
ip saddr 10.10.0.11 oifname "eth0" snat to 203.0.113.11
oifname "eth0" ip saddr 10.10.0.0/24 masquerade
}
}
EOF
systemctl enable nftables --now
nft list ruleset
Поддерживайте один источник истины — так проще управлять изменениями и откатами.
Отладка и наблюдаемость
Пара трюков сильно сокращает время диагностики:
- Счётчики правил: добавляйте
counterи смотрите их рост на нужных правилах. - Трассировка:
nft monitor traceпоказывает, по каким правилам прошёл пакет. - conntrack: смотрите активные NAT-сессии, чтобы понять, какой snat сработал.
nft -a list chain ip nat postrouting
nft monitor trace
conntrack -L -p tcp
ss -Snta
Если видите много дропов по дефолтному правилу, временно расширяйте логирование префиксом и отфильтровывайте в системном журнале. Главное — не оставляйте многословные «логирующие дропы» навсегда, чтобы не зашумлять диск.
Частые ошибки и нюансы платформы VDS
На виртуалках провайдеры зачастую включают антиспуфинг и ограничивают ARP/ND. Всё, что не относится к выданным вам адресам, будет отброшено на границе.
- Отсутствует форвардинг: проверьте
net.ipv4.ip_forward=1и что его не переопределяет другой файл в/etc/sysctl.d. - Смешение порядка NAT-правил: специфичные
snatдолжны быть выше, затем общийmasqueradeили подсетныйsnat. - Непредсказуемый исходящий IP при MASQUERADE: при нескольких адресах на интерфейсе используйте явный
snat to. - Эфемерные порты закончились: ограничьте диапазон, распределите нагрузку, включите
random,persistent, при необходимости увеличьтеnet.ipv4.ip_local_port_range. - Ассиметрия маршрутов: при нескольких аплинках используйте policy routing и стабильный выбор исходного IP.
- Перегрев логов: не оставляйте тотальное логирование дропов на проде, включайте адресное логирование по мере отладки.
IPv6: кратко о NAT66
В IPv6 принято избегать NAT, но иногда нужен NPTv6 или NAT66. В nftables есть ip6 nat и masquerade для IPv6, но прежде убедитесь, что это соответствует вашей политике и требованиям приложений. В большинстве случаев лучше выдать IPv6-адреса конечным хостам и фильтровать исходящие соединения без NAT.
Надёжный порядок действий (чеклист)
- Определите целевую схему: один публичный IP с
masqueradeили несколько адресов с явнымиsnat to. - Включите форвардинг и настройте
rp_filterв умеренный режим при необходимости. - Создайте каркас nftables:
inet filterиip nat, политика deny-by-default наforward. - Добавьте разрешающие правила на
forwardдля нужных сервисов (web, DNS, ICMP) и запреты для лишних (например, SMTP). - Опишите NAT: специфичные
snatсверху, далее общийmasqueradeили общийsnat. - При нескольких аплинках включите маркировку и
ip ruleдля policy routing. - Сделайте конфигурацию персистентной в
/etc/nftables.confи проверьте после перезапуска. - Проведите отладку:
nft monitor trace, счётчики,conntrack, снимкиss.
Итоги
SNAT в nftables на VDS — это про ясную архитектуру и контроль. Для одного адреса используйте masquerade, для нескольких — явные snat to с понятным порядком правил. Контролируйте исходящий трафик через строгий фильтр на forward, избегайте «дырок», и подключайте policy routing там, где действительно нужны разные выходы. Такая база выдерживает рост нагрузки, аудиты безопасности и сложности продакшена без ночных сюрпризов.
Если под задачи NAT и фильтра вам требуется пересмотреть ресурсы сервера, загляните в материал о том, как выбрать план VDS по CPU и RAM. Для L4-проксирования трафика пригодится разбор балансировки TCP/UDP в Nginx Stream.


