Як налаштувати 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 ... працює: якщо sudoers не встановлює umask_override, що вимкнено за замовчуванням, sudo поєднує вашу umask зі своїм значенням за замовчуванням 0022 і ніколи не послаблює права, тому файл tee створює з режимом 600. Ключі витікають, якщо виконати цей pipe без 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 додає необов’язковий попередньо поширений ключ — по одному рядку в кожен конфігураційний файл.
Інтерфейс сервера: /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, єдина адреса тунелю, якою володіє цей клієнт. Якщо призначити двом пірам однакову дозволену 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 не дозволяє wg0 до wg0. Піри отримують доступ до інтернету, але не один до одного; для однорангового VPN додайте iifname "wg0" oifname "wg0" accept. Доступ піра до самого сервера не належить до завдань цього ланцюжка: пакети, адресовані серверу, проходять через input hook, а не forward, тому рішення приймає ланцюжок input. У наведеному вигляді він дозволяє піру підключатися через SSH і більше ні до чого, навіть виконувати ping до 10.8.0.1. Додайте там iifname "wg0" icmp type echo-request accept, а також по одному правилу iifname "wg0" для кожного сервісу, який має бути доступним лише через тунель. Це особливо важливо, якщо сервер одночасно є віддаленим середовищем розробки, де 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 все одно потрібен повний цикл вимкнення та ввімкнення. Відкличте доступ за допомогою sudo wg set wg0 peer <public key> remove, а потім видаліть блок із файлу, інакше peer повернеться під час наступного перезавантаження набору peer.
Типові відмови та повідомлення, які ви побачите
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). Після цього невідповідність ключів відображається як відкидання пакета з недійсним MAC.
Handshake працює, але інтернету немає. ping 10.8.0.1 виконується успішно, але ping 1.1.1.1 завершується тайм-аутом: відсутня переадресація або 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]. У звичайному каналі з 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 не використовує сертифікати, 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, він самостійно відновить роботу.
Стан кожного вузла займає мало місця, а криптографічні операції виконуються в ядрі. Тому гранична продуктивність залежить від процесора та доступної пропускної здатності вашого VPS, а не від параметрів цього конфігураційного файлу. Виміряйте її за допомогою iperf3 через тунель, а не покладайтеся на опубліковані показники. Під час масштабування найбільше навантаження створюють операційні процеси. Кожному вузлу потрібна унікальна IP-адреса тунелю. Ручне редагування шістдесяти блоків [Peer] призводить до появи дублікатів AllowedIPs, тому генеруйте конфігурації скриптом. Один сервер — це одна UDP-кінцева точка та одна точка відмови. WireGuard не підтримує кластеризацію, тому для резервування потрібен другий сервер із власними ключами. Ротація ключів залишається ручною, тому зафіксуйте, хто зберігає кожен ключ і як його відкликати. Коли такого обліку стає забагато для текстового файлу, зазвичай поверх того самого ядрового рівня передавання даних розгортають площину керування. Self-hosted сервер NetBird виконує розподіл адрес, передавання даних про вузли та керування ключами налаштування, які інакше довелося б обробляти вручну. Якщо самостійне розгортання цієї площини керування означає ще один зайвий сервер, Tailscale розміщує її замість вас. Його безкоштовний план Personal охоплює шістьох користувачів із необмеженою кількістю пристроїв користувача та на початку до 50 ресурсів із тегами, наприклад серверів, чого достатньо, щоб більшість особистих інфраструктур не потребували оплати. Після цього тарифікація залежить від людей, а не від машин, тому фактична вартість для домогосподарства або невеликої команди визначається кількістю користувачів із обліковими даними, а не кількістю вузлів, які вам довелося б вручну додавати до wg0.conf. У цій схемі облік маршрутів split tunnel у AllowedIPs замінюється на оголошення приватних діапазонів через subnet router. Такий маршрут оголошується один раз з одного VPS і централізовано затверджується, замість того щоб вставляти його в кожен клієнтський файл. Чи варто використовувати цей підхід, залежить від того, до яких ресурсів фактично може отримати доступ розміщена площина керування. Вона ніколи не зберігає ключі, якими шифрується ваш трафік, але визначає, які вузли дізнаються один про одного.
Для всього цього потрібні Linux-сервер під вашим контролем, публічна IP-адреса, ядро, до якого можна завантажити модуль, і firewall, яким ви керуєте повністю.
FAQ
Чому рукостискання WireGuard ніколи не завершується?
wg show, у якому немає latest handshake, означає, що пакети не надходять або не приймаються. Перевірте UDP 51820 у firewall VPS і в окремому мережевому firewall вашого провайдера, підтвердьте хост і порт Endpoint, а потім перевірте, що ключі не переплутані: блок [Peer] на клієнті має містити публічний ключ сервера. sudo tcpdump -ni any udp port 51820 на сервері показує, чи надходять пакети взагалі; dmesg повідомляє лише про помилки рукостискання WireGuard після ввімкнення динамічного налагодження (echo module wireguard +p | sudo tee /sys/kernel/debug/dynamic_debug/control), після чого невідповідність ключів відображається як відкидання через недійсний 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 = у конфігурації клієнта?
У режимі повного тунелю без рядка DNS = клієнт зберігає resolver, отриманий від локальної мережі, і ці запити передаються незашифрованими через локальний канал, тоді як решта трафіку проходить через тунель. Спрямуйте DNS на публічний resolver або запустіть unbound/dnsmasq, прив’язаний до 10.8.0.1, і відкрийте udp dport 53 iifname "wg0" у вхідному ланцюжку.
Що насправді контролює 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 перед будь-якими іншими діями.