Як налаштувати WireGuard VPN на VPS
Покрокова інструкція з налаштування WireGuard на Linux VPS: конфігурація wg0.conf, IP forwarding, NAT та вирішення помилок з handshake та віртуалізацією.
Що ви створюєте
Налаштування WireGuard VPN на власному сервері займає близько сорока рядків конфігурації: одна пара ключів, один файл інтерфейсу, один параметр sysctl, одне правило NAT та одне дозволяюче правило у firewall. Процес встановлення є простим, тому більша частина цього посібника присвячена типовим проблемам: правам доступу до ключів, AllowedIPs, перенаправленню трафіку та DNS.
WireGuard — це тунель Layer 3 у ядрі. Він є основним компонентом, починаючи з Linux 5.6, тому Ubuntu 24.04 та Debian 13 постачаються з ним без необхідності встановлення зовнішніх модулів. Тут немає узгодження шифрів, центрів сертифікації або етапу введення логіна/пароля: peer (пір) — це публічний ключ разом із IP-адресами, які цей ключ може використовувати. Пакет, що не пройшов перевірку MAC, відкидається без відповіді, тому порт не відповідає на сканування. З іншого боку: сервер автентифікації відсутній, тому для позбавлення доступу необхідно видалити peer на сервері.
Спочатку перевірте віртуалізацію
WireGuard потребує ядра, у яке можна завантажити модуль. На KVM VPS він працює одразу. При контейнеризації з використанням спільного ядра хоста — OpenVZ, LXC — перша команда поверне помилку RTNETLINK answers: Operation not supported. У такому разі використовується реалізація wireguard-go у userspace. Спочатку перевірте тип віртуалізації за допомогою 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 додає опціональний попередньо спільний ключ (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 піра (peer) на стороні сервера — це /32, єдина адреса тунелю, яка належить клієнту. Якщо призначити двом пірам однакову allowed IP, адреса перейде до того, хто був налаштований останнім, а перший пір перестане отримувати трафік без виведення помилок. Не встановлюйте 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 потрібен egress інтерфейс — мережева карта з доступом до інтернету, а не wg0. Не припускайте назву eth0; дізнайтеся назву вашого інтерфейсу через ip route show default, оскільки сучасні образи використовують назви на кшталт enp1s0 або ens3.
Firewall: the port, and the forward path
One nftables file covers filter and NAT. Write /etc/nftables.conf — it flushes the existing ruleset, so skip this on a box already managed by ufw or 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
}
}Apply it with sudo systemctl enable --now nftables, keeping a second SSH session open: policy drop plus a typo in the SSH rule locks you out of your own server. Note what the forward chain does not allow — wg0 to wg0. Peers reach the internet, not each other; add iifname "wg0" oifname "wg0" accept for a peer-to-peer VPN. The same chain governs what a peer may touch on the server itself, which matters when the box doubles as a remote development box running Claude Code in tmux and you would rather not expose that side of it publicly.
On a ufw box: ufw allow 51820/udp, DEFAULT_FORWARD_POLICY="ACCEPT" in /etc/default/ufw, and a *nat POSTROUTING MASQUERADE rule at the top of /etc/ufw/before.rules.
Налаштування через systemd
sudo systemctl enable --now wg-quick@wg0
sudo wg showwg-quick створює інтерфейс, додає адреси та встановлює маршрути на основі AllowedIPs. enable --now — це критична частина: ручне налаштування wg-quick up wg0 зникне після наступного перезавантаження, а оновлення ядра також потребують перезавантаження.
Конфігурація клієнта та налаштування, яке всі розуміють неправильно
[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, прив'язаний до адреси тунелю, або віртуальні машини для nested-virtualisation, що працюють на тому ж хості, залишаються доступними для пірів і невидимими для всіх інших.
Для вхідного трафіку це список контролю доступу (ACL). Дешифрований пакет від піра, адреса джерела якого відсутня в AllowedIPs цього піра, відкидається. Саме тому сервер містить 10.8.0.2/32 для ноутбука: запис 0.0.0.0/0 дозволив би цьому клієнту підміняти будь-яку адресу в тунелі.
PersistentKeepalive потрібен для клієнтів за NAT, де роутер тримає UDP-мапінг відкритим лише під час передачі пакетів. Коли термін дії закінчується, сервер більше не може зв'язатися з клієнтом. PersistentKeepalive = 25 підтримує мапінг відкритим — налаштовуйте цей параметр на клієнті, а не на сервері з публічною IP-адресою.
DNS та витік, який ніхто не помічає
При використанні AllowedIPs = 0.0.0.0/0 без рядка no DNS = клієнт використовує resolver, отриманий від локальної мережі — роутер у кафе за адресою 192.168.1.1. Цей маршрут є більш специфічним, ніж маршрут за замовчуванням, тому DNS-запити передаються через локальний канал у відкритому вигляді, тоді як інший трафік проходить через тунель. Трафік є приватним, але список імен — ні.
Є два правильних варіанти. Направити DNS на публічний resolver (DNS = 9.9.9.9), тоді запити йтимуть через тунель і виходитимуть з вашого сервера, хоча цей resolver все одно їх бачитиме. Або запустити unbound або dnsmasq, прив'язані до 10.8.0.1, налаштувати DNS = 10.8.0.1 та додати udp dport 53 iifname "wg0" accept до input chain — якщо налаштувати цей рядок і не вказати resolver, DNS-вирішення взагалі не працюватиме.
На клієнтах 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-only (Address, DNS, PostUp), а syncconf застосовує зміни без розриву активних сесій. Команда оновлює лише пірів: якщо змінено Address, все одно потрібно виконати повний down/up. Щоб відкликати доступ, використайте sudo wg set wg0 peer <public key> remove, а потім видаліть блок із файлу, інакше він знову з'явиться під час наступного перезавантаження.
Режими відмови та відповідні рядки
Handshake не завершується. wg show відображає піра без 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), і при цьому невідповідність ключів відображається як invalid-MAC drop.
Handshake працює, інтернету немає. ping 10.8.0.1 виконується успішно, але ping 1.1.1.1 видає timeout: відсутнє перенаправлення (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 відсутній або вказує на резолвер, недоступний зсередини тунелю.
Деякі HTTPS-сайти не завантажуються. SSH та ping працюють; важкі сторінки зависають. Це проблема path MTU: тунель додає службову інформацію, і один із проміжних вузлів скидає завеликі пакети без надсилання ICMP-повідомлення у відповідь. Зменште MTU у конфігурації клієнта [Interface] — спробуйте 1420, потім 1380, потім 1280.
Інтерфейс не запускається. 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 не використовує сертифікати, CA або термін дії, тому кожен клієнт отримує новий пару ключів.
Використовуйте паралельну міграцію: WireGuard на UDP 51820 може працювати одночасно з OpenVPN на 1194 на тому самому сервері. Встановіть wg0, переведіть клієнтів по одному, а потім зупиніть стару службу. Модель логінів/паролів та відкликання ключів OpenVPN не переноситься. Якщо вам потрібні облікові записи або журнал аудиту, реалізуйте це поверх WireGuard.
Backups, upgrades, and what strains at scale
/etc/wireguard — це сервер. Зробіть резервну копію (sudo tar czf wg-backup.tgz -C /etc wireguard, mode 600, зберігайте поза сервером), і ви зможете розгорнути систему на новому VPS за лічені хвилини. Якщо ви втратите приватний ключ сервера, доведеться перевипускати конфігурацію для кожного клієнта, оскільки клієнти використовують public key сервера для перевірки. Оновлення — це звичайна apt upgrade плюс перезавантаження для оновлення ядра; wg-quick@wg0 відновиться автоматично, якщо ви увімкнули цю функцію.
Стан кожного піра (peer) займає мало місця, а криптографія виконується в ядрі. Тому обмеженням є процесор та пропускна здатність вашого VPS, а не налаштування конфігурації. Вимірюйте навантаження за допомогою iperf3 через тунель, а не орієнтуйтеся на публічні показники. При масштабуванні основне навантаження припадає на операційні процеси. Кожному піру потрібна унікальна IP-адреса тунелю. Ручне редагування шістдесяти блоків [Peer] призводить до появи дублікатів AllowedIPs: генеруйте конфігурації за допомогою скриптів. Один сервер — це одна UDP-точка входу та єдина точка відмови. WireGuard не підтримує кластеризацію: для забезпечення відмовостійкості потрібен другий сервер з власними ключами. Ротація ключів залишається ручною, тому ведіть облік того, хто володіє яким ключем і як анулювати ключ.
Для всього цього потрібен Linux-сервер під вашим повним контролем: публічна IP-адреса, ядро з можливістю завантаження модуля та firewall, яким ви керуєте повністю.
FAQ
Чому WireGuard не завершує handshake?
wg show, де вказано peer без latest handshake, означає, що пакети не надходять або не приймаються. Перевірте UDP 51820 на фаєрволі VPS та на окремому мережевому фаєрволі провайдера. Переконайтеся, що Endpoint та порт вказані вірно. Перевірте, чи не переплутані ключі: у блоці [Peer] клієнта має бути public key сервера. Команда sudo tcpdump -ni any udp port 51820 на сервері показує, чи надходять пакети взагалі; dmesg звітує про помилки handshake лише після увімкнення dynamic debug (echo module wireguard +p | sudo tee /sys/kernel/debug/dynamic_debug/control), при цьому невідповідність ключів відображається як invalid-MAC drop.
Тунель підключено, але інтернету немає. Що не так?
Якщо ping 10.8.0.1 працює, а ping 1.1.1.1 видає timeout, проблема у forwarding або NAT. Переконайтеся, що sysctl net.ipv4.ip_forward повертає 1 і що цей параметр встановлено в /etc/sysctl.d/, а не лише через sysctl -w, який зникає після перезавантаження. Перевірте назву правила masquerade для вашого реального вихідного інтерфейсу з ip route show default — це enp1s0 або ens3, рідше eth0.
Чи потрібен рядок DNS = у конфігурації клієнта?
При повному тунелі без рядка DNS = клієнт використовує резолвер, отриманий з локальної мережі. Такі запити передаються у відкритому вигляді через локальний канал, хоча весь інший трафік йде через тунель. Вкажіть DNS на публічний резолвер або запустіть unbound/dnsmasq на 10.8.0.1 та відкрийте udp dport 53 iifname "wg0" в input chain.
Що саме контролює AllowedIPs?
Цей параметр виконує дві функції. На вихід це таблиця маршрутизації: трафік, що відповідає AllowedIPs піра, шифрується та надсилається цьому піру. На вхід це список контролю доступу: розшифрований пакет, джерело якого знаходиться поза межами AllowedIPs цього піра, відкидається. Саме тому на стороні сервера вказано /32 для кожного клієнта, тоді як на стороні клієнта може бути лише 0.0.0.0/0.
Чи буде WireGuard працювати на будь-якому VPS?
На KVM VPS модуль працює в ядрі, додаткове налаштування не потрібне. На контейнеризованій віртуалізації, що використовує ядро хоста (наприклад, OpenVZ або LXC), modprobe wireguard видає помилку Operation not supported, тому використовується реалізація wireguard-go у userspace. Виконайте sudo modprobe wireguard && echo ok перед іншими діями.