Введение: зачем объединять сети через WireGuard
Два офиса, два облачных сегмента, две изолированные подсети - 192.168.1.0/24 в Москве и 10.0.0.0/24 в Амстердаме. Задача: сервер базы данных в одной локации должен быть доступен разработчикам из другой, принтер в бухгалтерии - виден из головного офиса, внутренний портал - открываться по прямому IP без проброса портов наружу. Решение - постоянный site-to-site VPN-туннель, который объединяет сети на третьем уровне OSI, делая маршрутизацию между ними прозрачной для приложений.
WireGuard для этой задачи подходит лучше аналогов. Конфигурация описывается десятком строк, производительность упирается в пропускную способность канала, а не в процессор шлюза, кодовая база в 4000 строк проверяется аудиторами за вечер. Для сравнения: OpenVPN требует разбираться с PKI, сертификатами и режимами работы (tap/bridging против tun/routing), IPsec - согласовывать две фазы IKE, политики шифрования и следить за совместимостью реализаций. WireGuard же работает по принципу «поднял интерфейс - получил туннель», маршрутизация настраивается стандартными средствами ядра.
К концу этой статьи вы получите готовую конфигурацию двух шлюзов, правила iptables для форвардинга, статические маршруты на хостах и защиту от асимметричной маршрутизации - проблему, которая ломает связность при малейшей ошибке в таблице маршрутизации.
Схема сети и требования к окружению
Топология состоит из двух шлюзов с WireGuard, каждый из которых является точкой входа в свою локальную сеть. Шлюзы имеют публичные IP (или динамические с DDNS), за ними - внутренние подсети с хостами. Туннельный интерфейс wg0 на каждом шлюзе получает адрес из выделенной транспортной подсети 10.255.0.0/30, которая используется только для связи между шлюзами и не анонсируется во внутренние сети.
Требования к окружению: ядро Linux версии 5.6 или новее (встроенный модуль wireguard), права root на обоих шлюзах, отключенная блокировка IP-форвардинга в sysctl. Если у провайдера динамический IP - потребуется DDNS-имя и скрипт периодического обновления Endpoint. На хостах внутри сетей - любая ОС, умеющая добавлять статические маршруты.
Исходные данные: IP-адреса и подсети
| Параметр | Site A (Москва) | Site B (Амстердам) |
|---|---|---|
| Публичный IP шлюза | 203.0.113.10 | 198.51.100.20 |
| Внутренняя подсеть | 192.168.1.0/24 | 10.0.0.0/24 |
| IP шлюза во внутренней сети | 192.168.1.1 | 10.0.0.1 |
| IP туннельного интерфейса wg0 | 10.255.0.1/30 | 10.255.0.2/30 |
Эти адреса зафиксированы для всех примеров ниже. Подставляйте свои значения при адаптации конфигурации.
Установка WireGuard и базовая настройка шлюзов
Установка на Ubuntu/Debian выполняется одной командой: apt install wireguard. Для CentOS/RHEL 8+ сначала подключите EPEL: dnf install epel-release, затем dnf install wireguard-tools. Пакет wireguard-tools содержит утилиту wg и скрипт wg-quick для управления интерфейсами. Модуль ядра wireguard в современных дистрибутивах встроен, проверяется командой modinfo wireguard.
Генерация ключей на каждом шлюзе:
umask 077
wg genkey | tee /etc/wireguard/private.key | wg pubkey > /etc/wireguard/public.key
Приватный ключ никогда не покидает шлюз. Публичный ключ передаётся администратору второй стороны - его нужно прописать в секции [Peer] удалённого узла.
Конфигурация шлюза Site A
Файл /etc/wireguard/wg0.conf на шлюзе в Москве:
[Interface]
Address = 10.255.0.1/30
ListenPort = 51820
PrivateKey = <приватный_ключ_шлюза_A>
[Peer]
PublicKey = <публичный_ключ_шлюза_B>
Endpoint = 198.51.100.20:51820
AllowedIPs = 10.255.0.2/32, 10.0.0.0/24
Директива AllowedIPs выполняет две функции: фильтрует входящие пакеты (принимаются только с указанных источников) и добавляет маршруты в таблицу ядра при использовании wg-quick. Здесь мы разрешаем трафик от туннельного адреса удалённого шлюза и всей внутренней подсети Site B.
Конфигурация шлюза Site B
Файл /etc/wireguard/wg0.conf на шлюзе в Амстердаме:
[Interface]
Address = 10.255.0.2/30
ListenPort = 51820
PrivateKey = <приватный_ключ_шлюза_B>
[Peer]
PublicKey = <публичный_ключ_шлюза_A>
Endpoint = 203.0.113.10:51820
AllowedIPs = 10.255.0.1/32, 192.168.1.0/24
Зеркальная конфигурация: тот же порт, адрес из той же транспортной подсети, в AllowedIPs - туннельный адрес и внутренняя сеть противоположной стороны.
Запуск туннеля и проверка статуса:
systemctl enable --now wg-quick@wg0
wg show
Вывод wg show должен показать интерфейс wg0 с публичным ключом пира, последнее рукопожатие (last handshake) и счётчики переданных/принятых данных. Если рукопожатия нет - проверьте, что порт 51820/UDP открыт на обоих шлюзах и не блокируется файрволом провайдера.
Настройка кросс-маршрутизации: как обеспечить связность всех узлов
Туннель поднят, шлюзы пингуют друг друга по адресам 10.255.0.1 и 10.255.0.2. Но хост 192.168.1.50 из Site A не достаёт до хоста 10.0.0.30 из Site B. Причина: шлюзы знают маршруты до удалённых подсетей (благодаря AllowedIPs в конфиге wg-quick), а хосты внутри сетей - нет. Пакет от 192.168.1.50 до 10.0.0.30 уходит на шлюз по умолчанию 192.168.1.1, тот пересылает его в туннель, удалённый шлюз доставляет адресату. Ответный пакет от 10.0.0.30 до 192.168.1.50 идёт на шлюз 10.0.0.1, но тот не знает, что подсеть 192.168.1.0/24 доступна через туннель, и отправляет пакет в интернет по маршруту по умолчанию. Связности нет.
Решение состоит из двух обязательных шагов: включение IP-форвардинга на шлюзах и добавление статических маршрутов на хостах или на их шлюзах по умолчанию.
Включение IP-форвардинга на шлюзах
Без форвардинга шлюз отбрасывает пакеты, адресованные не ему. Проверяем текущее состояние:
sysctl net.ipv4.ip_forward
Если выводит net.ipv4.ip_forward = 0 - форвардинг выключен. Включаем на лету и фиксируем в конфиге:
sysctl -w net.ipv4.ip_forward=1
echo 'net.ipv4.ip_forward=1' >> /etc/sysctl.conf
Эта настройка выполняется на обоих шлюзах. Перезагрузка не требуется, изменения применяются мгновенно.
Добавление статических маршрутов на конечных хостах
Самый надёжный способ - явно указать хосту, что удалённая подсеть доступна через его локальный WireGuard-шлюз. На Linux-хосте внутри Site A добавляем маршрут до подсети Site B:
ip route add 10.0.0.0/24 via 192.168.1.1
Для постоянного маршрута в Ubuntu/Debian добавляем строку в /etc/network/interfaces или создаём файл в /etc/netplan/. В CentOS/RHEL - создаём файл /etc/sysconfig/network-scripts/route-eth0 с содержимым 10.0.0.0/24 via 192.168.1.1.
На Windows-хосте выполняем от администратора:
route -p add 10.0.0.0 mask 255.255.255.0 192.168.1.1
Ключ -p делает маршрут постоянным (persistent), он переживёт перезагрузку.
На хосте внутри Site B зеркально добавляем маршрут до подсети Site A:
ip route add 192.168.1.0/24 via 10.0.0.1
Централизованная маршрутизация через шлюз по умолчанию
Если в сети десятки хостов, править каждый вручную неудобно. Альтернатива - прописать статический маршрут на основном роутере офиса (том, который раздаёт интернет и является шлюзом по умолчанию для всех устройств). На роутере добавляется правило: «трафик до 10.0.0.0/24 отправлять на 192.168.1.1» (где 192.168.1.1 - WireGuard-шлюз, который может быть отдельной машиной, а не роутером).
Плюсы: не нужно трогать хосты, новый хост автоматически получает доступ к удалённой сети. Минусы: весь межсетевой трафик проходит через роутер и WireGuard-шлюз последовательно, добавляя лишний хоп. При выходе из строя роутера теряется и локальная, и межсетевая связность. Выбор между вариантами зависит от размера сети и требований к отказоустойчивости.
Настройка iptables для форвардинга трафика
IP-форвардинг в ядре включён, маршруты прописаны - пакеты должны ходить. Но если на шлюзе активен iptables с политикой FORWARD в DROP (а это стандартная практика безопасности), пакеты будут молча отброшены файрволом. Нужно явно разрешить форвардинг между интерфейсами.
Правила фильтрации FORWARD
Базовая настройка на каждом шлюзе: разрешаем трафик из туннеля во внутреннюю сеть и обратно, а также установившийся (established) и связанный (related) трафик для корректной работы соединений. Всё остальное - DROP.
iptables -P FORWARD DROP
iptables -A FORWARD -i wg0 -o eth0 -j ACCEPT
iptables -A FORWARD -i eth0 -o wg0 -j ACCEPT
iptables -A FORWARD -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
Первая строка устанавливает политику по умолчанию - запретить всё. Вторая и третья разрешают форвардинг между туннельным и физическим интерфейсами в обоих направлениях. Четвёртая - пропускает ответные пакеты уже установленных соединений, без неё тот же ping будет работать только в одну сторону.
Если внутренних интерфейсов несколько (например, VLAN'ы), добавляйте правила для каждой пары wg0 <-> eth1, wg0 <-> eth2 или используйте более гибкую конфигурацию с nftables.
Настройка NAT (MASQUERADE) на шлюзе
Нужен ли NAT? Если хосты внутри Site A отправляют пакеты в Site B с исходным адресом 192.168.1.50, а Site B знает маршрут обратно до 192.168.1.0/24 - NAT не нужен, пакеты ходят с оригинальными адресами. Это прозрачная маршрутизация, она сохраняет информацию об источнике и удобна для аудита.
Но если по какой-то причине обратный маршрут настроить невозможно (например, удалённая сеть не под вашим контролем, и её администратор не хочет добавлять маршруты), применяется SNAT/MASQUERADE - подмена исходного адреса на адрес туннельного интерфейса шлюза:
iptables -t nat -A POSTROUTING -o wg0 -j MASQUERADE
Это правило на шлюзе Site A заменит адрес 192.168.1.50 на 10.255.0.1 при выходе пакета в туннель. Для хостов Site B трафик будет выглядеть как исходящий от шлюза Site A. Обратная связность обеспечивается автоматически, но теряется идентификация источника. Используйте этот вариант как временное решение или когда прозрачная маршрутизация невозможна.
Сохранение правил iptables между перезагрузками:
apt install iptables-persistent
netfilter-persistent save
На CentOS/RHEL: service iptables save.
Предотвращение асимметричной маршрутизации и других проблем
Асимметричная маршрутизация возникает, когда пакет от хоста A к хосту B идёт одним путём, а ответ возвращается другим. В контексте site-to-site VPN классический сценарий: хост из Site A отправляет запрос через WireGuard-шлюз, удалённый хост Site B получает его и отправляет ответ, но его шлюз по умолчанию (основной роутер, не участвующий в VPN) не знает о туннеле и выбрасывает пакет в интернет. Результат: TCP-соединение не устанавливается (SYN уходит, SYN-ACK не возвращается), ping работает только в одну сторону.
Типичные симптомы и их причины
Первый признак - односторонний ping. Хост A пингует хост B, ответы приходят. Хост B пингует хост A - тишина. Причина: на хосте B отсутствует маршрут до подсети хоста A, либо маршрут есть, но с более высокой метрикой, и пакет уходит через интерфейс по умолчанию.
Второй признак - TCP-соединения зависают на этапе установки. curl к веб-серверу в удалённой сети висит и отваливается по таймауту. Причина та же: TCP handshake требует двусторонней связности, и если обратный путь сломан - соединение не поднимется.
Третий признак - нестабильная пропускная способность, периодические обрывы. Здесь возможна проблема с MTU: туннель добавляет заголовки, эффективный MTU уменьшается, большие пакеты фрагментируются или отбрасываются. Решается установкой MTU = 1420 в секции [Interface] конфига WireGuard.
Использование tcpdump и traceroute для отладки
Диагностика начинается с проверки таблицы маршрутизации на обоих шлюзах и конечных хостах:
ip route get 10.0.0.30 from 192.168.1.50
Эта команда показывает, через какой интерфейс и шлюз ядро отправит пакет от указанного источника к указанному назначению. Если выводит не тот интерфейс - проблема в таблице маршрутизации.
Трассировка пути пакета на шлюзе Site A:
tcpdump -i any -n host 10.0.0.30 and icmp
Запустите на шлюзе и инициируйте ping с хоста 192.168.1.50 до 10.0.0.30. Вы должны увидеть ICMP-запрос на интерфейсе eth0 (вход из локальной сети), затем на wg0 (выход в туннель), затем ICMP-ответ на wg0 (вход из туннеля) и на eth0 (выход в локальную сеть). Если пакет появляется на wg0, но ответ не приходит - проблема на удалённой стороне. Если пакет не доходит до wg0 - проблема с форвардингом или iptables на локальном шлюзе.
traceroute с хоста покажет каждый хоп. Если маршрут обрывается на шлюзе - проверяйте iptables и таблицу маршрутизации именно на нём.
Для сложных случаев с несколькими провайдерами и балансировкой поможет policy routing на основе ip rule и отдельных таблиц маршрутизации. Маркируйте пакеты, пришедшие из туннеля, и направляйте ответный трафик строго в тот же туннель - это гарантирует симметричность пути.
Работа с динамическими IP-адресами
Если провайдер меняет публичный IP одного из шлюзов, туннель падает. WireGuard разрешает Endpoint только при старте интерфейса, повторный DNS-запрос не выполняется. Решение - DDNS-имя в директиве Endpoint и периодический перезапуск туннеля для обновления IP.
Настройте DDNS-клиент на шлюзе с динамическим IP (например, ddclient для DynDNS, No-IP или Cloudflare). В конфиге WireGuard укажите доменное имя:
Endpoint = site-b.ddns.example.com:51820
Создайте скрипт /usr/local/bin/wg-refresh.sh:
#!/bin/bash
# Разрешаем DNS и сравниваем с текущим Endpoint
CURRENT_IP=$(wg show wg0 endpoints | awk '{print $2}' | cut -d: -f1)
RESOLVED_IP=$(dig +short site-b.ddns.example.com)
if [ "$CURRENT_IP" != "$RESOLVED_IP" ]; then
systemctl restart wg-quick@wg0
fi
Добавьте запуск в cron каждые 5 минут: */5 * * * * /usr/local/bin/wg-refresh.sh. Более элегантный вариант - systemd timer с тем же интервалом. Перезапуск туннеля вызывает кратковременный обрыв соединений (доли секунды), для большинства приложений это незаметно.
Заключение: проверка и дальнейшие шаги
Финальная проверка: с хоста 192.168.1.50 выполните ping 10.0.0.30. Ответ должен прийти с задержкой, равной сумме задержек интернет-канала между площадками плюс несколько миллисекунд на обработку. Проверьте двусторонний доступ к сервисам: откройте веб-интерфейс приложения в удалённой сети по прямому IP, подключитесь по SSH, смонтируйте NFS-шару.
Результат: две географически разделённые сети работают как одна. Хосты видят друг друга по реальным IP, трафик шифруется с использованием ChaCha20Poly1305, накладные расходы на шифрование минимальны. Вы получили прозрачную связность без проброса портов, без поднятия отдельных туннелей на каждом хосте, без централизованного VPN-сервера.
Дальнейшие шаги по масштабированию: добавление третьего узла сводится к прописыванию ещё одной секции [Peer] в конфигах существующих шлюзов и добавлению соответствующих маршрутов. Для отказоустойчивости можно поднять два туннеля между площадками с разными провайдерами и настроить OSPF через FRR - это тема отдельного руководства. Документация WireGuard доступна на официальном сайте проекта, там же - сравнение производительности с IPsec и OpenVPN в эталонных тестах.
Если вы строите более сложную топологию с несколькими площадками и динамической маршрутизацией, обратите внимание на наше руководство WireGuard как полноценный маршрутизатор: создание mesh-сети и настройка маршрутизации. Для настройки селективной маршрутизации DNS-запросов через туннель - селективная маршрутизация DNS через VPN: готовые конфигурации WireGuard, OpenVPN и strongSwan. Если сравниваете WireGuard с альтернативами для специфичных сценариев - V2RayTUN и WireGuard: сравнение подходов к маршрутизации для DevOps.