Как настроить WireGuard VPN на своем VPS
Пошаговое руководство по установке WireGuard на Linux VPS. Разберем генерацию ключей, настройку wg0.conf, IP forwarding, NAT и типичные ошибки при проверке рукопожатия.
Что вы создаете
WireGuard VPN на собственном сервере — это около сорока строк конфигурации: одна пара ключей, один файл интерфейса, одна настройка sysctl, одно правило NAT и одно правило межсетевого экрана. Установка тривиальна, поэтому большая часть этого руководства посвящена возможным сбоям, правам доступа к ключам, AllowedIPs, пересылке пакетов и DNS.
WireGuard — это туннель третьего уровня (Layer 3), работающий на уровне ядра. Он включен в основную ветку Linux начиная с версии 5.6, поэтому Ubuntu 24.04 и Debian 13 поставляются с ним без необходимости в сторонних модулях. Здесь нет согласования шифров, центра сертификации или этапа ввода имени пользователя и пароля: пир определяется открытым ключом и списком IP-адресов, которые этот ключ может использовать. Пакет, не прошедший проверку MAC, отбрасывается без ответа, поэтому порт не реагирует на сканирование. Обратная сторона: сервера аутентификации не существует, поэтому для отзыва доступа необходимо удалить пир на самом сервере.
Сначала проверьте виртуализацию
Для работы WireGuard требуется ядро, в которое можно загрузить модуль; на KVM VPS это работает сразу. При использовании контейнерной виртуализации, разделяющей ядро хоста (OpenVZ, LXC), первая команда завершится ошибкой RTNETLINK answers: Operation not supported, и в качестве альтернативы придется использовать реализацию wireguard-go в пространстве пользователя. Сначала выполните проверку с помощью sudo modprobe wireguard && echo ok.
Генерация ключей без утечек
Файл /etc/wireguard/server.key, доступный для чтения всем, равносилен отсутствию VPN. Стандартная строка umask 077 && wg genkey | sudo tee ... работает: если в sudoers не задан параметр umask_override (который по умолчанию выключен), sudo объединяет ваш umask со своим значением по умолчанию 0022 и не делает его менее строгим, поэтому файл, создаваемый tee, имеет права 600. Утечка ключей происходит при выполнении конвейера без umask 077, с обычным umask 022 или на системе, где в sudoers включен umask_override. Установите права доступа явно, и это перестанет иметь значение.
sudo apt update && sudo apt install -y wireguard nftables
sudo install -d -m 700 /etc/wireguard
sudo sh -c 'umask 077; wg genkey > /etc/wireguard/server.key'
sudo sh -c 'wg pubkey < /etc/wireguard/server.key > /etc/wireguard/server.pub'
sudo chmod 600 /etc/wireguard/server.keyСгенерируйте пару для клиента таким же способом. wg genpsk добавляет опциональный общий ключ (pre-shared key), который указывается одной строкой в каждом конфигурационном файле.
Интерфейс сервера: /etc/wireguard/wg0.conf
[Interface]
Address = 10.8.0.1/24
ListenPort = 51820
PrivateKey = <contents of /etc/wireguard/server.key>
[Peer]
PublicKey = <laptop public key>
PresharedKey = <psk, optional>
AllowedIPs = 10.8.0.2/32chmod 600 его; предупреждение при запуске о том, что файл доступен всем пользователям, означает, что вы пропустили этот шаг. Address — это адрес сервера внутри туннеля, включающий маску всей подсети VPN. Выберите диапазон, который не встретится вам в реальных сетях; 192.168.1.0/24 конфликтует с половиной домашних маршрутизаторов, за которыми находятся ваши клиенты, из-за чего туннель будет молча проигрывать локальному маршруту.
AllowedIPs пира на стороне сервера — это /32, единственный адрес в туннеле, принадлежащий данному клиенту. Если назначить двум пирам одинаковый AllowedIPs, трафик будет переходить к тому, кто был настроен последним, а первый перестанет получать данные без вывода каких-либо ошибок. Оставьте SaveConfig не заданным, иначе wg-quick down перезапишет этот файл на основе текущего состояния системы.
Превращение сервера в маршрутизатор
Сервер на базе Linux отбрасывает пакеты, адресованные не ему. По умолчанию пересылка пакетов (forwarding) и source NAT отключены.
printf 'net.ipv4.ip_forward = 1\nnet.ipv6.conf.all.forwarding = 1\n' \
| sudo tee /etc/sysctl.d/99-wireguard.conf
sudo sysctl --system
sysctl net.ipv4.ip_forwardПростая команда sysctl -w работает только до следующей перезагрузки, после чего маршрутизация незаметно прекращается. Для NAT требуется исходящий интерфейс — сетевая карта, через которую осуществляется выход в интернет, а не wg0. Не полагайтесь на eth0; узнайте имя своего интерфейса с помощью ip route show default, так как в современных образах используются имена вроде enp1s0 или ens3.
Firewall: порт и путь пересылки
Один файл nftables охватывает фильтрацию и NAT. Напишите /etc/nftables.conf, это сбрасывает существующий набор правил, поэтому пропустите этот шаг, если сервер уже управляется через ufw или Docker.
#!/usr/sbin/nft -f
flush ruleset
table inet filter {
chain input {
type filter hook input priority filter; policy drop;
ct state established,related accept
iif lo accept
tcp dport 22 accept
udp dport 51820 accept
}
chain forward {
type filter hook forward priority filter; policy drop;
ct state established,related accept
iifname "wg0" oifname "enp1s0" accept
}
}
table ip nat {
chain postrouting {
type nat hook postrouting priority srcnat; policy accept;
ip saddr 10.8.0.0/24 oifname "enp1s0" masquerade
}
}Примените конфигурацию с помощью sudo systemctl enable --now nftables, оставив открытой вторую SSH-сессию: policy drop вместе с опечаткой в правиле SSH заблокирует вам доступ к собственному серверу. Обратите внимание, что цепочка forward не разрешает, wg0 по wg0. Узлы получают доступ в интернет, но не друг к другу; добавьте iifname "wg0" oifname "wg0" accept для VPN типа «равный-равному». То, к чему узел может обращаться на самом сервере, не входит в задачи этой цепочки: пакеты, адресованные самому серверу, проходят через хук input, а не forward, поэтому решение принимает цепочка input. В текущем виде она позволяет узлу обращаться только к SSH и больше ни к чему, даже ping до 10.8.0.1 не пройдет. Добавьте iifname "wg0" icmp type echo-request accept в это место, плюс по одному правилу iifname "wg0" на каждый сервис, который должен быть доступен только через туннель. Это важно, когда сервер одновременно выступает в роли удаленной машины для разработки, где в tmux запущен Claude Code, и вы не хотите выставлять эту часть наружу.
На сервере с ufw: ufw allow 51820/udp, DEFAULT_FORWARD_POLICY="ACCEPT" в /etc/default/ufw и правило *nat POSTROUTING MASQUERADE в начале файла /etc/ufw/before.rules.
Запуск через systemd
sudo systemctl enable --now wg-quick@wg0
sudo wg showwg-quick создает интерфейс, добавляет адреса и устанавливает маршруты, полученные из AllowedIPs. enable --now — это важная часть: настройки, выполненные вручную через wg-quick up wg0, сбрасываются после перезагрузки, а обновления ядра требуют перезагрузки системы. Юнит, который не запустился автоматически после перезагрузки, остается неактивным, пока кто-либо не попытается установить соединение. Поэтому использование drop-in файла OnFailure= в wg-quick@wg0 с настройкой уведомлений на ваш собственный ntfy сервер — самый простой способ узнать о сбое через телефон, не дожидаясь жалоб от пользователей, потерявших доступ.
Клиентская конфигурация и настройка, в которой все ошибаются
[Interface]
PrivateKey = <laptop private key>
Address = 10.8.0.2/32
DNS = 10.8.0.1
[Peer]
PublicKey = <server public key>
Endpoint = vpn.example.com:51820
AllowedIPs = 0.0.0.0/0, ::/0
PersistentKeepalive = 25AllowedIPs выполняет две задачи одновременно, и их смешение — основной источник путаницы при работе с WireGuard.
Для исходящего трафика это таблица маршрутизации. Пакет, адрес назначения которого соответствует AllowedIPs пира, шифруется и отправляется этому пиру. 0.0.0.0/0, ::/0 направляет всё в туннель, создавая полный туннель, где сервер становится маршрутом по умолчанию. Раздельный туннель (split tunnel) — это более узкий список: AllowedIPs = 10.8.0.0/24, 10.20.0.0/16 передает VPN-трафик и трафик одной частной сети за сервером, а всё остальное продолжает идти через локальный маршрут. Именно этот узкий список позволяет полностью скрыть сервисы от публичного интернета, например, приватный экземпляр Nextcloud на VPS, привязанный к адресу туннеля, или лабораторные виртуальные машины, работающие на том же узле, остаются доступными для пиров и невидимыми для всех остальных.
Для входящего трафика это список контроля доступа. Расшифрованный пакет от пира, чей адрес источника отсутствует в AllowedIPs этого пира, отбрасывается. Именно поэтому в конфигурации сервера для ноутбука указан 10.8.0.2/32: запись 0.0.0.0/0 позволила бы этому клиенту подменять любой адрес внутри туннеля.
PersistentKeepalive предназначен для клиентов за NAT, где маршрутизатор поддерживает UDP-маппинг открытым только во время передачи пакетов. Когда время жизни маппинга истекает, сервер теряет возможность связаться с клиентом. PersistentKeepalive = 25 поддерживает маппинг в активном состоянии; настраивайте этот параметр на клиенте, а не на сервере с публичным IP-адресом.
DNS и утечка, которую никто не замечает
При использовании AllowedIPs = 0.0.0.0/0 и отсутствии строки DNS = клиент продолжает использовать резолвер, полученный из локальной сети, например, роутер кафе по адресу 192.168.1.1. Этот маршрут является более специфичным, чем маршрут по умолчанию, поэтому DNS-запросы уходят через локальный канал в открытом виде, в то время как весь остальной трафик передается через туннель. Сам трафик остается приватным, но список запрашиваемых имен — нет.
Есть два честных варианта. Укажите DNS на публичный резолвер (DNS = 9.9.9.9), тогда запросы пройдут через туннель и выйдут с вашего сервера, хотя резолвер все равно будет их видеть. Либо запустите unbound или dnsmasq, привязанные к 10.8.0.1, установите DNS = 10.8.0.1 и добавьте udp dport 53 iifname "wg0" accept в цепочку input; после настройки этой строки и удаления резолвера разрешение имен прекратится вовсе.
На клиентах Linux wg-quick применяет DNS через resolvconf; если этот параметр отсутствует, вы получите resolvconf: command not found. Установите openresolv или настройте PostUp = resolvectl dns %i 10.8.0.1 на клиенте с systemd-resolved.
Добавление и удаление пиров без разрыва туннеля
Перезапуск интерфейса для добавления пользователя отключает всех подключённых пользователей. Добавьте блок [Peer] в wg0.conf, затем примените набор пиров без остановки интерфейса.
sudo bash -c 'wg syncconf wg0 <(wg-quick strip wg0)'wg-quick strip выводит конфигурацию без ключей, используемых только wg-quick (Address, DNS, PostUp), а syncconf применяет отличия, не прерывая активные сеансы. Обновляются только пиры: для изменённого Address требуется полный останов и повторный запуск интерфейса. Отзовите доступ с помощью sudo wg set wg0 peer <public key> remove, затем удалите блок из файла, иначе он вернётся при следующей перезагрузке набора пиров.
Типовые сбои и сообщения об ошибках
Рукопожатие не завершается. wg show показывает пир без latest handshake, а в логах клиента отображается:
Handshake for peer 1 (10.0.0.10:51820) did not complete after 5 seconds, retrying (try 2)Данные не поступают или не принимаются. Проверьте по порядку: открыт ли UDP 51820 на брандмауэре VPS и на сетевом экране провайдера (это отдельная настройка в большинстве панелей управления); верны ли адрес и порт в Endpoint; не перепутаны ли ключи. В блоке [Peer] на клиенте должен быть указан публичный ключ сервера, и наоборот. Использование приватного ключа или собственного публичного ключа клиента приводит именно к такому симптому. sudo tcpdump -ni any udp port 51820 на сервере показывает, доходят ли пакеты вообще. Модуль ядра по умолчанию не выводит сообщений; записи WireGuard появляются в dmesg только после включения динамической отладки (echo module wireguard +p | sudo tee /sys/kernel/debug/dynamic_debug/control). При включенной отладке несовпадение ключей будет отображаться как отброшенные пакеты с ошибкой invalid-MAC.
Рукопожатие проходит, интернета нет. ping 10.8.0.1 выполняется успешно, но ping 1.1.1.1 выдает таймаут: отсутствует пересылка (forwarding) или NAT. Убедитесь, что sysctl net.ipv4.ip_forward содержит 1, затем наблюдайте за счетчиками во время пинга с клиента с помощью sudo nft list ruleset или sudo iptables -t nat -L POSTROUTING -n -v. Нулевые значения на правиле маскарадинга означают, что неверно указано имя исходящего интерфейса; растущий счетчик при отсутствии ответов указывает на политику цепочки forward.
Интернет работает, имена не разрешаются. ping 1.1.1.1 проходит успешно, а curl https://example.com возвращает Could not resolve host. Отсутствует строка DNS или в ней указан резолвер, недоступный изнутри туннеля.
Некоторые HTTPS-сайты зависают. SSH и ping работают нормально, но загрузка тяжелых страниц останавливается. Это проблема path MTU: туннель добавляет накладные расходы, и какой-то узел на пути отбрасывает слишком большие пакеты, не отправляя обратно ICMP-сообщение. Установите MTU в блоке [Interface] на клиенте. На обычном канале с MTU 1500 значение wg-quick уже выбирает 1420 (1500 минус 80 байт накладных расходов), поэтому установка 1420 ничего не изменит: попробуйте 1380, затем 1280. Если снижение MTU устранило зависания, но скорость все еще низкая, не подбирайте значения наугад, а воспользуйтесь поиском реального path MTU методом деления пополам и ограничением TCP MSS, что также позволит исключить причины, не связанные с туннелем.
Интерфейс не запускается. Address already in use означает, что порт UDP 51820 занят другим процессом. Cannot find device wg0 после неудачного up обычно означает, что конфигурация была отклонена; изучите journalctl -u wg-quick@wg0 -n 50.
Миграция со Streisand или OpenVPN
Проект Streisand больше не поддерживается, его репозиторий находится в архиве. Использование VPN на базе устаревших инструментов автоматизации создает угрозу безопасности. Прямое обновление невозможно, а PKI от OpenVPN не подлежит конвертации: в WireGuard нет сертификатов, центров сертификации и сроков действия, поэтому для каждого клиента генерируется новая пара ключей.
Выполняйте миграцию параллельно: WireGuard на UDP 51820 может работать одновременно с OpenVPN на 1194 на одном сервере. Разверните wg0, переводите клиентов по одному, а затем остановите старую службу. Модель аутентификации по имени пользователя и паролю, а также механизм отзыва сертификатов из OpenVPN не переносятся; если вам необходимы учетные записи или аудит действий, реализуйте эти функции поверх WireGuard.
Резервное копирование, обновления и масштабируемость
/etc/wireguard и есть сервер. Создавайте его резервные копии (sudo tar czf wg-backup.tgz -C /etc wireguard, права 600, храните вне сервера), и вы сможете восстановить его на новом VPS за считанные минуты. Потеря приватного ключа сервера потребует перевыпуска всех конфигураций клиентов, так как клиенты фиксируют публичный ключ сервера. Обновления выполняются стандартной командой apt upgrade с последующей перезагрузкой при обновлении ядра, а wg-quick@wg0 запустится автоматически, если вы включили эту опцию.
Состояние каждого узла занимает мало места, а криптография работает на уровне ядра, поэтому предел производительности определяется процессором и пропускной способностью вашего VPS, а не этой конфигурацией. Измеряйте её с помощью iperf3 через туннель, вместо того чтобы доверять заявленным цифрам. При масштабировании основной проблемой становится эксплуатация. Каждому узлу нужен уникальный IP в туннеле, а ручное редактирование шестидесяти блоков [Peer] приводит к появлению дубликатов AllowedIPs: генерируйте конфигурации с помощью скрипта. Один сервер — это одна UDP-конечная точка и одна точка отказа, а в WireGuard нет встроенной кластеризации: избыточность означает наличие второго сервера со своими ключами. Ротация ключей остается ручной операцией, поэтому записывайте, у кого какой ключ и как отозвать конкретный. Когда такой учет перерастает текстовый файл, стандартным решением становится использование control plane поверх того же data plane в ядре, и самохостируемый сервер NetBird берет на себя распределение адресов, узлов и ключей настройки, которые иначе пришлось бы делать вручную. Если запуск такого control plane для вас — это лишняя нагрузка, Tailscale сделает это за вас, и их бесплатный тариф Personal покрывает шесть пользователей с неограниченным количеством устройств и, для начала, до 50 помеченных ресурсов, таких как серверы, чего достаточно для большинства личных парков техники. После этого порога оплата взимается за людей, а не за машины, поэтому то, сколько на самом деле платит домохозяйство или небольшая команда зависит от количества людей с доступом, а не от количества узлов, которые вы вручную вписывали бы в wg0.conf. С этой стороны учет split-tunnel AllowedIPs превращается в анонсирование ваших частных диапазонов через subnet router, который настраивается один раз на одном VPS и одобряется централизованно, вместо того чтобы копировать его в каждый файл клиента. Стоит ли это того — зависит от того, к чему может получить доступ хостируемый control plane, и он никогда не хранит ключи, шифрующие ваш трафик, хотя и решает, какие узлы узнают друг о друге.
Для всего этого требуется Linux-сервер под вашим контролем, публичный IP, ядро с возможностью загрузки модулей и файрвол, которым вы управляете полностью.
FAQ
Почему рукопожатие WireGuard никогда не завершается?
wg show при указании пира без latest handshake означает, что пакеты не доходят или не принимаются. Проверьте UDP 51820 на брандмауэре VPS и на внешнем сетевом экране провайдера, подтвердите Endpoint хоста и порта, затем убедитесь, что ключи не перепутаны: блок [Peer] клиента должен содержать публичный ключ сервера. sudo tcpdump -ni any udp port 51820 на сервере показывает, приходят ли пакеты вообще; dmesg сообщает об ошибках рукопожатия WireGuard только после включения динамической отладки (echo module wireguard +p | sudo tee /sys/kernel/debug/dynamic_debug/control), после чего несовпадение ключей отображается как отброшенные пакеты с ошибкой invalid-MAC.
Туннель устанавливается, но интернета нет. Чего не хватает?
ping 10.8.0.1 работает, а ping 1.1.1.1 выдает таймаут — это указывает на проблемы с пересылкой (forwarding) или NAT. Убедитесь, что sysctl net.ipv4.ip_forward содержит 1 и что этот параметр задан в /etc/sysctl.d/, а не просто через sysctl -w, который сбрасывается после перезагрузки. Затем проверьте правило маскарадинга для вашего основного исходящего интерфейса в ip route show default, enp1s0 или ens3, реже в eth0.
Нужна ли строка DNS = в конфигурации клиента?
При использовании полного туннеля без строки DNS = клиент продолжает использовать DNS-резолвер, полученный из локальной сети. В этом случае запросы уходят в открытом виде через локальное соединение, в то время как весь остальной трафик идет через туннель. Укажите DNS публичный резолвер или запустите unbound/dnsmasq, привязанный к 10.8.0.1, и откройте udp dport 53 iifname "wg0" в цепочке input.
За что именно отвечает AllowedIPs?
Этот параметр выполняет две функции. На исходящем трафике это таблица маршрутизации: трафик, соответствующий AllowedIPs пира, шифруется и отправляется этому пиру. На входящем трафике это список контроля доступа: расшифрованный пакет, чей адрес источника не входит в AllowedIPs пира, отбрасывается. Именно поэтому на стороне сервера для каждого клиента указывается /32, тогда как на стороне клиента может быть указано 0.0.0.0/0.
Будет ли WireGuard работать на любом VPS?
На KVM VPS он работает с модулем ядра без дополнительной настройки. В контейнерной виртуализации, использующей общее ядро хоста, такой как OpenVZ или LXC, modprobe wireguard завершается ошибкой Operation not supported, и в качестве альтернативы используется реализация в пространстве пользователя wireguard-go. Запустите sudo modprobe wireguard && echo ok перед выполнением любых других действий.