Як налаштувати WireGuard VPN на власному VPS
Покрокове налаштування WireGuard на Linux VPS: ключі, wg0.conf, IP forwarding, NAT, AllowedIPs, DNS і помилка "Operation not supported" у LXC та OpenVZ.
Що ви налаштовуєте
WireGuard VPN на власному сервері — це приблизно 40 рядків конфігурації: одна пара ключів, один файл інтерфейсу, один параметр sysctl, одне правило NAT і одне правило у firewall. Встановлення просте, тому більша частина цього посібника присвячена типовим проблемам, правам доступу до ключів, AllowedIPs, маршрутизації та DNS.
WireGuard — це тунель Layer 3 у ядрі Linux, доступний у mainline починаючи з Linux 5.6. Тому Ubuntu 24.04 і Debian 13 постачають його без зовнішнього модуля. Узгодження шифрів, certificate authority і етапу з username/password немає: peer — це public key та IP-адреси, які може використовувати цей ключ. Пакет, який не проходить перевірку MAC, відкидається без відповіді, тому порт не відповідає на сканування. Недолік полягає в тому, що auth server не існує. Щоб відкликати доступ, потрібно видалити peer на сервері.
Спочатку перевірте віртуалізацію
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 ... ненадійний, оскільки sudo застосовує власну umask до файла, який створює tee. Явно встановіть права доступу.
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 додає необов’язковий попередньо спільний ключ, по одному рядку в кожній конфігурації.
Інтерфейс сервера: /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 вузла peer на стороні сервера — це /32, єдина адреса тунелю, що належить цьому клієнту. Якщо призначити двом вузлам peer однакову allowed IP, вона переміститься до вузла, налаштованого останнім, а перший припинить отримувати трафік без будь-якого повідомлення про помилку. Не залишайте SaveConfig невизначеним, інакше wg-quick down перезапише цей файл на основі поточного стану.
Перетворіть сервер на маршрутизатор
Linux-сервер відкидає пакети, адресовані не йому. За замовчуванням пересилання пакетів і 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 потрібен *egress*-інтерфейс — NIC, через який сервер отримує доступ до інтернету, а не wg0. Не припускайте, що це eth0; визначте свій інтерфейс за допомогою ip route show default, оскільки сучасні образи використовують такі імена, як enp1s0 або ens3`.
Фаєрвол: порт і шлях пересилання
Один файл 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 chain не дозволяє wg0 до wg0. Вузли отримують доступ до інтернету, але не один до одного; додайте iifname "wg0" oifname "wg0" accept для peer-to-peer VPN. Цей самий ланцюжок визначає, до чого peer може отримати доступ безпосередньо на сервері. Це важливо, якщо сервер також використовується як віддалене середовище розробки, де Claude Code працює в tmux, а ви не хочете публічно відкривати цю частину системи.
На сервері з ufw: ufw allow 51820/udp, DEFAULT_FORWARD_POLICY="ACCEPT" у /etc/default/ufw і правило POSTROUTING MASQUERADE *nat на початку /etc/ufw/before.rules.
Запустіть це через systemd
sudo systemctl enable --now wg-quick@wg0
sudo wg showwg-quick створює інтерфейс, додає адреси та встановлює маршрути, визначені на основі AllowedIPs. enable --now — важлива частина: виконаний вручну wg-quick up wg0 зникне після наступного перезавантаження, а оновлення ядра передбачають перезавантаження. Unit, який не запускається повторно після одного з таких перезавантажень, залишатиметься непомітним, доки хтось не спробує підключитися. Тому 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 спрямовує через тунель увесь трафік: це повний тунель, у якому сервер є маршрутом за замовчуванням. Розділений тунель містить вужчий список: 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 = клієнт продовжує використовувати 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 без розриву тунелю
Перезапуск інтерфейсу для додавання користувача розриває з’єднання всіх підключених клієнтів. Додайте блок [Peer] до wg0.conf, а потім перезавантажте набір peer без зупинки інтерфейсу.
sudo bash -c 'wg syncconf wg0 <(wg-quick strip wg0)'wg-quick strip виводить конфігурацію без ключів, які використовує лише wg-quick (Address, DNS, PostUp), а syncconf застосовує зміни без розриву активних сесій. Команда оновлює лише peer: після зміни Address все одно потрібно повністю виконати down/up. Відкличте доступ за допомогою sudo wg set wg0 peer <public key> remove, а потім видаліть блок із файлу. Інакше він знову з’явиться під час наступного перезавантаження.
Режими відмови та повідомлення, які ви побачите
Handshake не завершується. wg show показує peer без latest handshake, а клієнт записує в журнал:
Handshake for peer 1 (10.0.0.10:51820) did not complete after 5 seconds, retrying (try 2)Пакети або не надходять, або не приймаються. Перевірте по черзі: чи відкритий UDP 51820 у firewall VPS і в мережевому firewall вашого провайдера; у більшості панелей це окремий параметр; чи правильні адреса та порт Endpoint; чи не переплутані ключі. У блоці [Peer] на клієнті має бути вказаний публічний ключ сервера, і навпаки. Якщо вставити приватний ключ або власний публічний ключ клієнта, виникне саме цей симптом. sudo tcpdump -ni any udp port 51820 на сервері показує, чи надходять пакети взагалі. Модуль ядра за замовчуванням нічого не записує в журнал; повідомлення WireGuard з’являються в dmesg лише після ввімкнення dynamic debug (echo module wireguard +p | sudo tee /sys/kernel/debug/dynamic_debug/control). Якщо debug увімкнено, невідповідність ключів відображається як відкидання пакета через недійсний MAC.
Handshake працює, але інтернету немає. ping 10.8.0.1 завершується успішно, але ping 1.1.1.1 завершується тайм-аутом: не налаштовано forwarding або NAT. Перевірте, чи sysctl net.ipv4.ip_forward має значення 1, а потім відстежуйте лічильники під час ping із клієнта за допомогою sudo nft list ruleset або sudo iptables -t nat -L POSTROUTING -n -v. Нульова кількість пакетів у правилі masquerade означає, що в ньому вказано неправильну назву вихідного інтерфейсу. Якщо лічильник зростає, але відповідей немає, проблема, ймовірно, у політиці forward chain.
Інтернет працює, але імена не розпізнаються. ping 1.1.1.1 завершується успішно, а curl https://example.com повертає Could not resolve host. Відсутній рядок DNS або в ньому вказано resolver, недоступний із тунелю.
Деякі HTTPS-сайти зависають. SSH і ping працюють, але великі сторінки завантажуються не повністю. Причина — path MTU: тунель додає службові дані, а деякий проміжний канал відкидає завеликі пакети, не повертаючи повідомлення ICMP. Зменште MTU у клієнтському [Interface], спробуйте 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 не використовує сертифікати, CA або строки дії, тому для кожного клієнта потрібно створити нову пару ключів.
Виконуйте міграцію паралельно. 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 запускається автоматично, якщо ви його увімкнули.
Стан кожного peer невеликий, а криптографічні операції виконуються в ядрі. Тому обмеженням буде CPU та пропускна здатність вашого VPS, а не параметри цього конфігураційного файлу. Вимірюйте їх за допомогою iperf3 через тунель, а не покладайтеся на опубліковані показники. У масштабі найбільше навантаження створюють операційні процеси. Кожному peer потрібна унікальна IP-адреса тунелю. Ручне редагування шістдесяти блоків [Peer] призводить до появи дублікатів AllowedIPs, тому генеруйте конфігурації скриптом. Один сервер — це один UDP endpoint і одна точка відмови. WireGuard не підтримує кластеризацію, тому резервування означає використання другого сервера з власними ключами. Ротація ключів залишається ручною. Зафіксуйте, хто володіє кожним ключем і як його відкликати. Коли такий облік стає складнішим за ведення текстового файлу, зазвичай використовують control plane поверх того самого kernel data plane. Self-hosted NetBird server виконує розподіл адрес, розповсюдження інформації про peer і керування setup keys, які інакше довелося б налаштовувати вручну. Якщо самостійне розгортання control plane додає ще один сервер, Tailscale може розмістити його замість вас. Його безкоштовний план охоплює шість користувачів із необмеженою кількістю пристроїв, чого достатньо, щоб більшості особистих інфраструктур не доводилося за це платити. Після цього вартість визначається кількістю користувачів, а не серверів. Тому фактична вартість для родини або невеликої команди залежить від кількості людей із обліковими записами, а не від кількості peer, які довелося б вручну додавати до wg0.conf. У такій схемі облік split-tunnel AllowedIPs замінюється публікацією приватних діапазонів через subnet router. Їх можна один раз оголосити з одного VPS і централізовано схвалити замість копіювання в кожен клієнтський файл. Доцільність такого переходу залежить від того, до яких ресурсів фактично має доступ hosted control plane. Він ніколи не зберігає ключі, якими шифрується ваш трафік, але визначає, які peer дізнаються один про одного.
Для всього цього потрібні Linux-сервер під вашим контролем, публічна IP-адреса, ядро, до якого можна завантажити модуль, і firewall, яким ви повністю керуєте.
FAQ
Чому handshake WireGuard не завершується?
wg show, у якому для peer немає latest handshake, означає, що пакети не надходять або не приймаються. Перевірте UDP 51820 у firewall VPS і в окремому мережевому firewall вашого провайдера, підтвердьте Endpoint host і порт, а потім перевірте, чи не переплутані ключі: блок [Peer] клієнта має містити публічний ключ сервера. sudo tcpdump -ni any udp port 51820 на сервері показує, чи надходять пакети взагалі; dmesg повідомляє лише про помилки handshake WireGuard після ввімкнення динамічного debug (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, який зникає після перезавантаження. Потім перевірте, чи правило masquerade використовує фактичний зовнішній інтерфейс із ip route show default, enp1s0 або, рідше, ens3 чи eth0.
Чи потрібен рядок DNS = у конфігурації клієнта?
Якщо використовується full tunnel і рядка DNS = немає, клієнт зберігає resolver, отриманий від локальної мережі. Такі запити передаються у відкритому вигляді через локальне з’єднання, тоді як решта трафіку проходить через тунель. Вкажіть у DNS публічний resolver або запустіть unbound/dnsmasq, прив’язаний до 10.8.0.1, і відкрийте udp dport 53 iifname "wg0" у вхідному ланцюжку.
Що саме контролює AllowedIPs?
Він виконує 2 функції. Для вихідного трафіку це таблиця маршрутизації: трафік, що відповідає AllowedIPs peer, шифрується і надсилається цьому peer. Для вхідного трафіку це список контролю доступу: розшифрований пакет, джерело якого не входить до AllowedIPs цього peer, відкидається. Тому на стороні сервера для кожного клієнта вказують /32, а на стороні клієнта можна вказати 0.0.0.0/0.
Чи працюватиме WireGuard на будь-якому VPS?
На KVM VPS він працює з модулем ядра без додаткового налаштування. У контейнерній віртуалізації зі спільним ядром host, наприклад OpenVZ або LXC, modprobe wireguard завершується помилкою Operation not supported. Запасний варіант — реалізація wireguard-go у userspace. Спочатку виконайте sudo modprobe wireguard && echo ok.