1. Гайды
  2. Сеть и диагностика

WireGuard между серверами: приватная сеть на два конфига

Туннель точка-точка, чтобы база, панель и ноды общались по приватным адресам, а не торчали портами в интернет.

Актуально для wireguard-tools 1.0.x, ядро 6.x, Debian 12 / Ubuntu 24.04Обновлено 23.09.2026 6 мин чтения

Типичная картина: панель на одном сервере, база или бот на другом, и порт PostgreSQL открыт всему интернету «временно, до выходных». WireGuard решает это двумя файлами по десять строк. Сложного здесь ничего нет, зато есть пара мест, где легко ошибиться и потом час смотреть на пустой wg show.

Схема

Два сервера, у обоих публичный IP:

Сервер A (панель)Сервер B (нода, база)
Публичный IP203.0.113.10198.51.100.20
Адрес в туннеле10.99.0.110.99.0.2
UDP-порт5182051820

Подсеть туннеля выбери такую, которая не пересекается с сетями хостера и Docker.

Установка

На обоих серверах:

apt install wireguard

Пакет ставит утилиты wg и wg-quick. Модуль ядра в современных Debian и Ubuntu уже есть.

Ключи

На каждом сервере свой ключ. Приватный ключ никуда не копируется, между серверами передаются только публичные.

cd /etc/wireguard
umask 077
wg genkey | tee private.key | wg pubkey > public.key
chmod 600 private.key
cat public.key

umask 077 делает так, что файлы сразу создаются с правами только для root. Именно так рекомендует man wg. Публичный ключ из public.key каждого сервера понадобится в конфиге другого.

Конфиги

Сервер A

/etc/wireguard/wg0.confini
[Interface]
Address = 10.99.0.1/24
ListenPort = 51820
PrivateKey = <содержимое private.key сервера A>

[Peer]
PublicKey = <содержимое public.key сервера B>
AllowedIPs = 10.99.0.2/32
Endpoint = 198.51.100.20:51820

Сервер B

/etc/wireguard/wg0.confini
[Interface]
Address = 10.99.0.2/24
ListenPort = 51820
PrivateKey = <содержимое private.key сервера B>

[Peer]
PublicKey = <содержимое public.key сервера A>
AllowedIPs = 10.99.0.1/32
Endpoint = 203.0.113.10:51820
chmod 600 /etc/wireguard/wg0.conf

В конфиге лежит приватный ключ. Если права шире, wg-quick при запуске предупредит: is world accessible.

Что значит каждая строка

[Interface] описывает этот сервер:

  • Address: адрес интерфейса wg0 в туннеле. Это ключ wg-quick, а не wg: его назначает скрипт через ip.
  • ListenPort: UDP-порт, на котором ждём пакеты. Если не указать, порт выбирается случайно, и второй стороне будет некуда стучаться.
  • PrivateKey: приватный ключ этого сервера. Обязателен.
  • MTU: необязательный, про него ниже.

[Peer] описывает другую сторону:

  • PublicKey: публичный ключ пира. По нему WireGuard понимает, кто прислал пакет. Обязателен.
  • AllowedIPs: какие адреса «принадлежат» этому пиру. Подробно ниже, это главная грабля.
  • Endpoint: публичный IP и порт пира. Необязателен: WireGuard сам обновляет его на адрес, с которого последний раз пришёл корректный пакет от пира. Но сторона без Endpoint не может начать соединение первой и ждёт, пока пир постучится сам. Между двумя серверами с постоянными IP проще прописать Endpoint на обоих.
  • PersistentKeepalive: раз в сколько секунд слать пустой пакет, чтобы NAT или stateful-файрвол не забыл соединение. По умолчанию выключен, и man wg прямо пишет, что большинству он не нужен. Ставь PersistentKeepalive = 25, только если одна из сторон за NAT (домашний сервер, некоторые облака) и к ней должны приходить соединения в тишине.

AllowedIPs: это таблица маршрутизации

AllowedIPs работает в обе стороны. На отправку это маршрут: пакет на адрес из списка шифруется ключом этого пира и уходит ему. На приём это фильтр: пакет от пира с исходным адресом не из списка выбрасывается. В документации WireGuard это называется Cryptokey Routing.

Кроме того, wg-quick сам добавляет в системную таблицу маршрутов всё, что перечислено в AllowedIPs.

Не делай так

Не пиши AllowedIPs = 0.0.0.0/0 в туннеле между серверами. Для wg-quick это сигнал сделать туннель маршрутом по умолчанию, и весь трафик сервера, включая ответы твоей SSH-сессии, уйдёт в туннель. Для точка-точка нужен только адрес пира: /32.

Если за сервером B есть целая сеть, которую надо видеть с A (например, подсеть Docker), добавь её через запятую: AllowedIPs = 10.99.0.2/32, 172.20.0.0/16. На B тогда понадобится ещё и форвардинг, но это уже другая история.

Файрвол

WireGuard слушает UDP. На каждом сервере открой порт, лучше только для IP второй стороны. Для конфига из гайда по nftables в цепочку input сервера A:

/etc/nftables.confbash
    # WireGuard только от сервера B
    ip saddr 198.51.100.20 udp dport 51820 accept
    # трафик внутри туннеля: например, PostgreSQL
    iifname "wg0" tcp dport 5432 accept

На сервере B то же самое, но с ip saddr 203.0.113.10. Если используешь ufw:

ufw allow from 198.51.100.20 to any port 51820 proto udp

Грабли

Вторая строка про wg0 не для красоты. С policy drop пакеты, пришедшие из туннеля, режутся так же, как из интернета. Туннель поднимется, handshake будет, а база отвечать не станет.

Не забудь, что сервис должен слушать адрес туннеля: база на 127.0.0.1 из wg0 не видна. Слушай на 10.99.0.1 или на всех интерфейсах, но тогда доступ снаружи режь файрволом.

Запуск

Проверочный запуск руками:

wg-quick up wg0
ping -c 3 10.99.0.2
wg show

Если всё работает, передаём управление systemd, чтобы туннель поднимался после перезагрузки:

wg-quick down wg0
systemctl enable --now wg-quick@wg0

wg-quick down перед этим обязателен: если интерфейс уже поднят руками, юнит упадёт с ошибкой already exists.

Добавил пира или поменял AllowedIPs? Не нужно ронять туннель: юнит умеет перечитывать конфиг через wg syncconf.

systemctl reload wg-quick@wg0

Проверка: wg show

wg show wg0

Смотри на строки у пира:

  • latest handshake: 1 minute, 5 seconds ago: ключи согласованы, туннель живой.
  • transfer: ... received, ... sent: счётчики трафика.

Если строки latest handshake нет совсем, handshake не было ни разу. Учти, что WireGuard молчалив: без трафика он ничего не шлёт. Сделай ping на адрес пира и посмотри ещё раз.

Для скриптов есть машиночитаемый вывод: время последнего handshake в Unix-времени, 0, если его не было.

wg show wg0 latest-handshakes

Его удобно проверять в самописном healthcheck из гайда про мониторинг.

Нет handshake: что проверить

  1. UDP-порт закрыт. Файрвол на сервере, security group у хостера, или порт слушает не тот. ss -ulpn | grep 51820 покажет, слушает ли сервер.
  2. Перепутаны ключи. В [Peer] должен стоять публичный ключ другого сервера. Классика: вставить свой публичный или чужой приватный. Сравни wg show wg0 public-key на B со строкой PublicKey в конфиге A.
  3. Неверный Endpoint. Опечатка в IP или порте, либо Endpoint нет на обеих сторонах, и никто не начинает первым.
  4. Handshake есть, но пинг не идёт. Смотри AllowedIPs: адрес пира должен в них входить. Затем файрвол на wg0.

MTU

По умолчанию wg-quick сам вычисляет MTU: берёт MTU маршрута до Endpoint (или маршрута по умолчанию) и вычитает 80 байт. На обычном интерфейсе с MTU 1500 получается 1420.

Руками задавать нужно, только если путь между серверами уже, чем MTU интерфейса (туннель у хостера, PPPoE). Проверить, проходит ли пакет полного размера без фрагментации, можно с DF-флагом: 1472 байта данных плюс 28 байт заголовков ICMP и IP дают 1500.

ping -M do -s 1472 -c 3 198.51.100.20

Если не проходит, уменьшай -s, пока не пройдёт. Найденный размер плюс 28 минус 80 и есть MTU туннеля. Задаётся в [Interface] на обеих сторонах:

/etc/wireguard/wg0.confini
[Interface]
Address = 10.99.0.1/24
ListenPort = 51820
PrivateKey = <...>
MTU = 1380

WireGuard и Россия

Важно

Всё выше про туннель между серверами за рубежом, и там WireGuard работает без сюрпризов. Как канал из России он не годится: по опыту сообщества классический WireGuard в РФ блокируют легко, зато между нодами за границей его не трогают. Выжимки и ссылки на обсуждения в базе: ТСПУ: серверная часть, приватные сети между серверами. Что сейчас работает для входа из РФ, смотри в методах обхода и хронологии блокировок.

Если туннель нужен между сервером в РФ и зарубежным, в той же базе есть отзывы про AmneziaWG и про схему с WARP. Проверяй на своём маршруте, прежде чем строить на этом прод.

Источники