OSEN-НИЙ SAAALEСкидка 50% на виртуальный хостинг и VDS
до 30.11.2025 Подробнее
Выберите продукт

SNAT на VDS: nftables MASQUERADE, несколько публичных IP и контроль исходящего трафика

Практическое руководство по SNAT в nftables на VDS. Разберём MASQUERADE для одного адреса, явный snat to при нескольких IP, белые списки на forward, метки и policy routing, отладку и типичные ловушки.
SNAT на VDS: nftables MASQUERADE, несколько публичных IP и контроль исходящего трафика

Задача и контекст

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.

Схема: VDS с eth0, мост br0, приватная подсеть и SNAT через nftables

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.

FastFox VDS
Облачный VDS-сервер в России
Аренда виртуальных серверов с моментальным развертыванием инфраструктуры от 195₽ / мес

Контроль исходящего трафика: белые списки и запреты

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 соответствующему публичному адресу.

Policy routing: метки fwmark и таблицы маршрутизации ip rule

Персистентность конфигурации

На большинстве дистрибутивов 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.

Надёжный порядок действий (чеклист)

  1. Определите целевую схему: один публичный IP с masquerade или несколько адресов с явными snat to.
  2. Включите форвардинг и настройте rp_filter в умеренный режим при необходимости.
  3. Создайте каркас nftables: inet filter и ip nat, политика deny-by-default на forward.
  4. Добавьте разрешающие правила на forward для нужных сервисов (web, DNS, ICMP) и запреты для лишних (например, SMTP).
  5. Опишите NAT: специфичные snat сверху, далее общий masquerade или общий snat.
  6. При нескольких аплинках включите маркировку и ip rule для policy routing.
  7. Сделайте конфигурацию персистентной в /etc/nftables.conf и проверьте после перезапуска.
  8. Проведите отладку: nft monitor trace, счётчики, conntrack, снимки ss.

Итоги

SNAT в nftables на VDS — это про ясную архитектуру и контроль. Для одного адреса используйте masquerade, для нескольких — явные snat to с понятным порядком правил. Контролируйте исходящий трафик через строгий фильтр на forward, избегайте «дырок», и подключайте policy routing там, где действительно нужны разные выходы. Такая база выдерживает рост нагрузки, аудиты безопасности и сложности продакшена без ночных сюрпризов.

Если под задачи NAT и фильтра вам требуется пересмотреть ресурсы сервера, загляните в материал о том, как выбрать план VDS по CPU и RAM. Для L4-проксирования трафика пригодится разбор балансировки TCP/UDP в Nginx Stream.

Поделиться статьей

Вам будет интересно

systemd-run: ограничиваем CPU и RAM для одноразовых задач и интерактивных команд OpenAI Статья написана AI (GPT 5)

systemd-run: ограничиваем CPU и RAM для одноразовых задач и интерактивных команд

Как быстро ограничить CPU и память для разовых команд без unit-файлов: используем systemd-run, transient units в режимах --service ...
OpenSearch на VDS: практический гид по памяти JVM heap, ISM-политикам и снапшотам OpenAI Статья написана AI (GPT 5)

OpenSearch на VDS: практический гид по памяти JVM heap, ISM-политикам и снапшотам

Поднимем OpenSearch на VDS: настроим JVM heap без сюрпризов с GC, спроектируем ISM с rollover и удалением, организуем регулярные s ...
ACME DNS‑01 через RFC2136: свой DNS‑API без облаков OpenAI Статья написана AI (GPT 5)

ACME DNS‑01 через RFC2136: свой DNS‑API без облаков

DNS‑01 решает выпуск wildcard и закрытых сервисов, но нужен API к авторитетному DNS. Покажу, как поднять свой «API» на RFC2136: BI ...