Выберите продукт

nftables + WireGuard: NAT, forward chain и policy routing без сюрпризов

Разбираем типовые схемы WireGuard на Linux с nftables: интернет через wg0, доступ LAN в VPN, split-tunnel и policy routing. Дам рабочие правила NAT/forward, примеры ip rule/ip route table и чек-лист диагностики rp_filter.
nftables + WireGuard: NAT, forward chain и policy routing без сюрпризов

Что именно мы собираем и где чаще всего ломается

WireGuard сам по себе — это «просто интерфейс» (обычно wg0) и набор маршрутов, которые добавляются на основании AllowedIPs. А nftables — это фильтрация и NAT. Ошибки появляются на стыке: маршруты есть, туннель поднят, но трафик не ходит из-за forward, не работает NAT, или пакеты молча дропает rp_filter при асимметрии маршрутов (особенно когда включаем policy routing).

Ниже — практическая конфигурация для трёх самых частых задач:

  • Клиенты WireGuard выходят в интернет через сервер (NAT masquerade на внешнем интерфейсе).
  • LAN to VPN: из локальной сети (или контейнерной сети) отправляем часть/весь трафик в VPN через wg0.
  • Policy routing: часть трафика идёт через VPN, остальное — напрямую, причём разделение делается не только по подсети, но и по источнику/марке.

Будем опираться на nftables с таблицами inet (фильтрация) и ip/ip6 (NAT). Примеры подойдут для Debian/Ubuntu и большинства современных дистрибутивов.

Минимальные вводные: интерфейсы, адреса и sysctl

Для примеров примем такие обозначения:

  • eth0 — внешний интерфейс сервера (интернет).
  • wg0 — WireGuard.
  • VPN-сеть: 10.7.0.0/24, сервер в ней 10.7.0.1.
  • LAN (или внутренняя сеть, которую нужно отправлять в VPN): 192.168.50.0/24.

Сразу включим маршрутизацию и приведём rp_filter в режим, совместимый с policy routing (иначе при «нестандартном» обратном пути пакеты будут отбрасываться как спуфинг).

sysctl -w net.ipv4.ip_forward=1
sysctl -w net.ipv4.conf.all.rp_filter=2
sysctl -w net.ipv4.conf.default.rp_filter=2
sysctl -w net.ipv4.conf.wg0.rp_filter=2

Значение rp_filter=2 — «loose mode»: проверка источника менее строгая и не ломает асимметричные маршруты. Если policy routing не используете, можно оставить 1, но на практике на VPN-шлюзах чаще безопаснее и стабильнее именно 2 (при условии, что фильтрация и политики на firewall настроены корректно).

Проверьте, что интерфейс wg0 реально поднят и имеет адрес:

wg show
ip addr show dev wg0
ip route

Схема интерфейсов wg0 и eth0 и маршрутов, которые появляются из AllowedIPs

WireGuard: как AllowedIPs влияет на маршруты и доступность сетей

AllowedIPs — это одновременно:

  • список «разрешённых» подсетей для конкретного peer (что принимать из туннеля);
  • и подсказка для роутинга (что отправлять в этот peer).

Типичная серверная конфигурация (упрощённо) может выглядеть так (показываю как текст; в реальности это файл /etc/wireguard/wg0.conf):

[Interface]
Address = 10.7.0.1/24
ListenPort = 51820
PrivateKey = SERVER_PRIVATE_KEY

[Peer]
PublicKey = CLIENT1_PUBLIC_KEY
AllowedIPs = 10.7.0.2/32

На клиенте, если хотите «весь интернет через VPN» (full-tunnel), часто ставят AllowedIPs = 0.0.0.0/0. Если нужен split-tunnel — указывают только нужные подсети (например, доступ к офису/серверной сети). Типичная ошибка «туннель есть, а трафик не уходит» — нужная сеть не попала в AllowedIPs, и маршрута просто нет.

Для сценария lan to vpn (когда не клиент, а сервер/шлюз отправляет LAN в VPN) маршруты удобно делать вручную через ip route + ip rule. Это даёт гибкость, но требует аккуратности с nftables и rp_filter.

nftables: базовая фильтрация для WireGuard (input/forward)

Обычно на сервере нужно разрешить:

  • входящий UDP на порт WireGuard (например, 51820/udp) на внешнем интерфейсе;
  • форвардинг между wg0 и eth0 (или между wg0 и LAN-интерфейсом), если сервер работает как маршрутизатор;
  • обратный трафик (stateful).

Ниже — компактный, но рабочий каркас. Важно: политика по умолчанию drop, разрешаем только необходимое. Угловые скобки в примерах экранированы, чтобы конфиг можно было копировать в терминал без «сюрпризов» от редакторов.

nft -f /dev/stdin << 'EOF'
flush ruleset

table inet filter {
  chain input {
    type filter hook input priority 0;
    policy drop;

    iif "lo" accept
    ct state established,related accept

    # SSH (пример)
    tcp dport 22 accept

    # WireGuard
    udp dport 51820 accept

    # ICMP полезен для диагностики MTU/PMTU
    ip protocol icmp accept
    ip6 nexthdr icmpv6 accept
  }

  chain forward {
    type filter hook forward priority 0;
    policy drop;

    ct state established,related accept

    # Разрешаем трафик из VPN в интернет
    iif "wg0" oif "eth0" accept

    # Опционально: интернет в VPN (нужно только если вы реально публикуете сети за peer)
    iif "eth0" oif "wg0" accept
  }

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

Ключевая точка здесь — цепочка forward. Очень частая ситуация: WireGuard поднят, маршруты есть, но forward по умолчанию drop (или вообще не настроен), и «lan to vpn» не работает.

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

NAT masquerade для WireGuard: когда нужен и где включать

NAT на сервере нужен, когда ваши VPN-клиенты выходят в интернет через этот сервер и в интернете о них «не знают». Самый типичный сценарий: клиент получает адрес 10.7.0.2, идёт на сайты, а внешняя сеть видит только публичный IP сервера.

В nftables NAT обычно делается в таблице ip nat (и отдельно ip6 nat для IPv6, если вы используете NAT66 — что встречается реже и требует отдельного проектирования).

nft -f /dev/stdin << 'EOF'
table ip nat {
  chain prerouting {
    type nat hook prerouting priority -100;
    policy accept;
  }

  chain postrouting {
    type nat hook postrouting priority 100;
    policy accept;

    # NAT для клиентов WireGuard, выходящих в интернет через eth0
    oif "eth0" ip saddr 10.7.0.0/24 masquerade
  }
}
EOF

Проверка, что NAT реально срабатывает:

nft list ruleset
conntrack -L | head
tcpdump -ni eth0 host 8.8.8.8

Если интернет с клиента не работает, чаще всего причина одна из трёх:

  • нет правила masquerade (или не тот oif);
  • пакеты блокируются в forward;
  • на клиенте не настроен DNS (туннель есть, но имена не резолвятся).

Если у вас много исходящих IP или сложная логика SNAT (например, разные подсети клиентов должны выходить разными адресами), пригодится отдельный разбор: как контролировать SNAT на нескольких IP в nftables.

LAN to VPN: отправляем локальную сеть в туннель через policy routing

Сценарий: у вас есть хост-шлюз с LAN-интерфейсом (или просто подсеть, приходящая на него через другой роутер), и вы хотите, чтобы 192.168.50.0/24 ходила в интернет или в удалённые сети через WireGuard.

Можно «сломать» default route и отправить весь трафик самого сервера в VPN, но чаще нужно аккуратно: только LAN — в VPN, а сам сервер продолжает жить напрямую (обновления, мониторинг, SSH и т.д.). Для этого подходит policy routing: отдельная таблица маршрутизации плюс ip rule.

Шаг 1. Создаём отдельную таблицу маршрутов

Добавим таблицу, например 100. В современных системах можно не редактировать /etc/iproute2/rt_tables, а просто использовать номер. Но если вы ведёте инфраструктуру командой, имя таблицы повышает читаемость (это уже на ваш вкус).

Маршрут по умолчанию в этой таблице — через wg0:

ip route add default dev wg0 table 100

Если вам нужно отправлять в VPN не весь интернет, а конкретные подсети за удалённой стороной, вместо default добавляйте конкретные маршруты, например:

ip route add 10.20.0.0/16 dev wg0 table 100

Шаг 2. Правило ip rule: кто пользуется этой таблицей

Самая понятная политика: весь трафик с источником 192.168.50.0/24 маршрутизируем по таблице 100.

ip rule add from 192.168.50.0/24 lookup 100 priority 1000

Проверяем:

ip rule show
ip route show table 100
ip route get 1.1.1.1 from 192.168.50.10

Шаг 3. nftables: разрешаем форвардинг LAN → wg0 и делаем NAT там, где нужно

Дальше зависит от того, требуется ли скрывать LAN-адреса в VPN. Обычно да: удалённая сторона не знает вашу 192.168.50.0/24, либо вы не хотите анонсировать маршруты. Тогда делаем NAT на выходе в wg0.

Правила фильтрации (forward):

nft add rule inet filter forward iif "lan0" oif "wg0" accept
nft add rule inet filter forward iif "wg0" oif "lan0" ct state established,related accept

Где lan0 — ваш интерфейс в локальную сеть (например, ens19, br0, vlan50).

NAT (если надо маскарадинг LAN в VPN):

nft add rule ip nat postrouting oif "wg0" ip saddr 192.168.50.0/24 masquerade

Важно: NAT на wg0 нужен не всегда. Если вы контролируете обе стороны и можете прописать маршруты, корректнее (и прозрачнее для логов) обойтись без NAT: на удалённой стороне добавить маршрут к вашей LAN через peer WireGuard. Но в реальности часто быстрее именно masquerade.

FastFox SSL
Надежные SSL-сертификаты
Мы предлагаем широкий спектр SSL-сертификатов от GlobalSign по самым низким ценам. Поможем с покупкой и установкой SSL бесплатно!

Policy routing по маркировке: когда одной сети from lookup мало

Иногда нужно отправлять через VPN не всю подсеть, а только отдельные сервисы или группы хостов, либо трафик, который вы предварительно пометили (например, по порту/назначению). Тогда удобно использовать fwmark и отдельную таблицу маршрутизации.

Схема такая:

  • nftables помечает пакеты меткой (meta mark);
  • ip rule выбирает таблицу по fwmark;
  • таблица отправляет трафик в wg0.

Пример: пометим весь трафик из LAN к конкретной сети (например, 203.0.113.0/24) и отправим его через VPN.

nft add chain inet filter mangle_prerouting '{ type filter hook prerouting priority -150; policy accept; }'
nft add rule inet filter mangle_prerouting iif "lan0" ip daddr 203.0.113.0/24 meta mark set 0x66
ip rule add fwmark 0x66 lookup 100 priority 900
ip route add default dev wg0 table 100

Замечания по практике:

  • При маркировке в сложных сценариях важно продумать обратный трафик (иногда нужна синхронизация меток через conntrack: ct mark).
  • Проверяйте ip route get с указанием источника на тестовом хосте (или в network namespace), прежде чем переносить в прод.

Схема policy routing: маркировка пакетов в nftables и выбор таблицы маршрутизации через ip rule

Диагностика: что смотреть, когда «не работает»

Набор команд, который обычно быстро локализует проблему между WireGuard, маршрутизацией и nftables.

1) WireGuard живой?

wg show
ip -s link show dev wg0

Если RX/TX счётчики на wg0 не растут — проблема до firewall: ключи, endpoint, NAT у клиента, блокировка UDP/51820.

2) Маршрут выбран тот, который вы ожидаете?

ip rule show
ip route show table main
ip route show table 100
ip route get 8.8.8.8 from 192.168.50.10

ip route get — лучший друг при policy routing: он показывает, через какой интерфейс и какой next-hop ядро реально отправит пакет.

3) nftables реально пропускает?

nft list ruleset
nft list chain inet filter forward

Если сомневаетесь, временно добавьте логирование на forward (аккуратно, чтобы не залить диск):

nft add rule inet filter forward limit rate 5/second log prefix "FW " flags all

Если вы ещё не уверены в своей «базе» по nftables, полезно держать под рукой материал про защитные приёмы на уровне firewall: SYNPROXY и практичный firewall на VDS.

4) rp_filter не режет ли обратный путь?

sysctl net.ipv4.conf.all.rp_filter
sysctl net.ipv4.conf.wg0.rp_filter
journalctl -k | tail -n 200

При «странных» маршрутах (особенно при split-tunnel и нескольких uplink’ах) rp_filter=1 — частая причина неочевидных потерь. Для VPN-шлюза с policy routing обычно выбирают 2 и компенсируют риски строгим firewall и отсутствием лишних маршрутов.

Частые грабли: короткий чек-лист

  • AllowedIPs не соответствует задумке: нет маршрута к нужной сети или, наоборот, случайно «утянули» весь интернет в VPN.
  • Нет разрешения в forward: WireGuard работает, но маршрутизатор «не маршрутизирует».
  • NAT стоит не там: для «клиенты в интернет» NAT нужен на внешнем интерфейсе; для «lan to vpn» — часто на wg0.
  • rp_filter в strict mode при policy routing: пакеты уходят одним путём, возвращаются другим — и отбрасываются.
  • MTU: если часть сайтов/протоколов «подвисает», проверьте PMTU, попробуйте уменьшить MTU на wg0 (часто 1420 или ниже в зависимости от оверхеда).

Как это аккуратно сделать в проде

Разделяйте изменения на слои и проверяйте по шагам:

  1. Поднимите WireGuard и убедитесь, что handshake идёт и пакеты ходят между адресами VPN.
  2. Добавьте маршрутизацию (ip route/ip rule) и проверьте ip route get.
  3. Только потом включайте nftables forward и NAT, контролируя счётчики/логи.

Если вы делаете VPN-шлюз на сервере, который также обслуживает продакшен (веб, базы, почта), держите под рукой консольный доступ и план отката: policy routing может неожиданно «увести» исходящие соединения и отрезать вас от сервера, если ошибиться с приоритетами ip rule или default route в таблицах.

Идеальный результат — когда вы можете словами объяснить: какие пакеты попадают в wg0, какие правила разрешают форвардинг, где именно применяется masquerade, и какая таблица маршрутизации выбирается по ip rule. Тогда конфигурация становится не магией, а предсказуемой системой.

Итог

Связка WireGuard + nftables на Linux отлично работает и в full-tunnel, и в split-tunnel, и в роли VPN-шлюза для LAN — если правильно разложить ответственность:

  • WireGuard отвечает за туннель и (частично) маршруты через AllowedIPs;
  • nftables отвечает за forward и NAT (masquerade);
  • policy routing (ip rule, ip route и отдельные таблицы) отвечает за то, кто и куда маршрутизируется через wg0 без поломки остальных потоков.

Если нужно — в следующем материале можно разобрать сохранение маркировок через conntrack (ct mark), несколько peer’ов с разными таблицами, а также IPv6-стратегию (без NAT и с корректным firewall).

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

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

Health-check и graceful failover в Nginx и HAProxy: active-passive, веса, slow start OpenAI Статья написана AI (GPT 5)

Health-check и graceful failover в Nginx и HAProxy: active-passive, веса, slow start

Разбираем, как настроить быстрый и при этом «мягкий» failover между бэкендами: отличие L4 и L7 health-check, active-passive и веса ...
SNI и default server в Nginx/Apache: почему отдаётся «чужой» сертификат и как это быстро исправить OpenAI Статья написана AI (GPT 5)

SNI и default server в Nginx/Apache: почему отдаётся «чужой» сертификат и как это быстро исправить

Если при HTTPS браузер показывает сертификат другого домена, чаще всего виноваты SNI и выбор default server (или первого vhost). Р ...
Linux 6.x: UDP GRO/GSO и USO (tx-udp-segmentation) для ускорения VPN OpenAI Статья написана AI (GPT 5)

Linux 6.x: UDP GRO/GSO и USO (tx-udp-segmentation) для ускорения VPN

UDP GRO/GSO и USO в Linux 6.x могут заметно поднять throughput в WireGuard и других UDP‑VPN, снижая нагрузку на CPU и softirq. В с ...