WireGuard «Handshake did not complete»: як визначити проблеми з NAT, портами й часом

Було корисно?

WireGuard зазвичай нудний — у найкращому значенні цього слова. Поки не перестає бути. Ви розгортаєте тунель, інтерфейс піднімається, а статус застоюється як дохла риба: latest handshake: (none) або «Handshake did not complete». Тим часом ваш виклик на чергування винахідливо починає вібрувати.

Ця помилка вводить в оману своєю простотою: одна сторона не може завершити криптографічний танець з іншою. Хитрість у тому, що корінна причина часто зовсім не в криптографії. Це може бути стан NAT, що закінчився, тихо заблокований UDP-порт, маршрут, що веде в нікуди, фрагментація через MTU, яка перетворює пакети на конфетті, або зсув часу, що активує захист від повторних пакетів надто ретельно.

Що насправді означає “handshake” (і чого він не означає)

Handshakе WireGuard — це невеликий швидкий обмін UDP-пакетами, який встановлює епохальні сесійні ключі. Якщо він завершується, ви побачите мітку часу «latest handshake» і передані байти. Якщо ні — інтерфейс все одно може виглядати як “up”, бо це просто інтерфейс; WireGuard не робить обіцянки «connected» у TCP-сенсі.

Це речення важливе, бо тут трапляється багато помилок при діагностиці. Люди бачать, що wg0 існує, і припускають наявність зв’язку. WireGuard не дає обіцянку; він пропонує можливість.

Також: «handshake did not complete» — це не одна баг, а клас відмов:

  • Пакети ніколи не покидають клієнт (локальний брандмауер, неправильна таблиця маршрутизації, помилкове ім’я хоста для endpoint).
  • Пакети виходять, але ніколи не доходять (ISP блокує UDP, upstream-брандмауер, невірний порт-форвард, CGNAT).
  • Пакети доходять, але відповідь не може повернутися (асиметрична маршрутизація, невідповідність NAT-мапінгу, rp_filter, egress-брандмауер).
  • Handshake завершується, але трафік не працює (AllowedIPs, маршрутизація, MTU, DNS). Це виглядає подібно, якщо ви дивитесь лише “ping”.
  • Handshake періодично падає (таймаут NAT, зміни endpoint при роумінгу, відсутність keepalive).
  • Handshake відхиляється (невірні ключі, застарілий peer, проблеми з часом/повторами). Менш поширено, але трапляється.

Цитата, бо це досі найкраща оперативна порада в цій темі: Надія — не стратегія. — Gene Kranz. Ви будете вимірювати, а не гадати.

Швидкий план діагностики (перевірити перше/друге/третє)

Якщо ви зробите лише одну річ з цієї статті — зробіть це в такому порядку. Вона скоротить час від «руйнує мені вечір» до «трохи дратує».

Перше: доведіть доступність UDP до серверного порту

  1. На сервері перевірте, що він слухає на очікуваному порті (wg show і ss -lunp).
  2. На сервері запустіть tcpdump на публічному інтерфейсі для цього UDP-порту.
  3. З клієнта ініціюйте трафік (навіть один ping через тунель або просто підніміть інтерфейс).
  4. Якщо серверний tcpdump нічого не бачить: проблема вище за WireGuard (NAT/порт-форвард/брандмауер/ISP/CGNAT/неправильна IP).

Друге: якщо пакети доходять, доведіть, що сервер відповідає і клієнт отримує відповіді

  1. Тримайте tcpdump на сервері: чи бачите ви вихідні UDP до клієнта?
  2. Запустіть tcpdump на клієнті: чи бачите ви вхідні UDP від сервера?
  3. Якщо сервер відповідає, але клієнт цього не бачить: NAT посередині, шлях назад або stateful-брандмауер відкидає пакети.

Третє: якщо handshakes відбуваються, але трафік не працює — налагоджуйте AllowedIPs, маршрути і MTU

  1. wg show має показувати недавній handshake і зростаючі лічильники.
  2. ip route get для IP-адрес призначення має вказувати на wg0, коли це потрібно.
  3. Ping з встановленим DF (або MSS clamp), щоб виявити MTU-проблеми.

Ось і все. Ви шукаєте перше зламане посилання в ланцюзі: «клієнт відправляє UDP → сервер отримує UDP → сервер відповідає → клієнт отримує відповідь → ключі встановлені → маршрути відправляють трафік у тунель → трафік переживає MTU».

Факти й контекст для дискусій з колегами

  • WireGuard спроектований мінімалістично. Кодова база відома своєю компактністю порівняно зі старішими VPN-стеками, що зменшує «невідомі невідомості» під час інцидентів.
  • Він використовує UDP за дизайном. Це уникaє проблем TCP-over-TCP і добре працює при роумінгу, але змушує мислити в термінах досяжності та стану NAT.
  • Handshake базується на Noise. Це сучасна сім’я протоколів для автентифікованого обміну ключами з акцентом на простоту та сильні властивості.
  • «AllowedIPs» одночасно ACL і політика маршрутизації. Це свідомий дизайн: потужно, але часто призводить до помилок.
  • WireGuard не дає індикатора життєздатності так, як цього очікують люди. Немає стану «connected»; ініціація handshake відбувається при наявності трафіку.
  • PersistentKeepalive існує головно для NAT. Якщо ви коли-небудь сварилися з готельним Wi‑Fi, ця опція — ввічливий спосіб підтримувати NAT-таблицю відкритою кожні N секунд.
  • Багато побутових роутерів мають короткі таймаути UDP. Нерідко мапінги вичерпуються через 30–120 секунд простою.
  • Carrier-grade NAT сьогодні звичне явище. Багато «публічних» підключень насправді не публічні з боку входу. Ніякий порт-форвард цього не виправить.
  • Захист від повторів чутливий до часу. Якщо годинники сильно відстають/випереджають, деякі легітимні пакети можуть трактуватися як повтори. Безпека робить свою справу; ви — той, хто переміщується в часі.

Жарт 1/2: NAT — як корпоративна служба підтримки: забуває, що ви існуєте, щойно ви припиняєте надсилати заявки.

Практична ментальна модель: пакети, стан і час

Думайте про handshake як про чотири умови, які мають одночасно виконуватись:

  1. Правильна адресація: клієнт знає endpoint сервера (IP:порт), сервер знає, куди відповідати.
  2. Двостороння доступність UDP: не «ping працює», а «UDP-пакети проходять в обидва боки».
  3. Стан тримається достатньо довго: NAT і брандмауери зберігають мапінги/стан довше, ніж потрібно для handshake і наступного трафіку.
  4. Криптографічна ідентичність збігається: публічні ключі й AllowedIPs співпадають; захист від повторів не спрацьовує через проблеми з часом.

Ця модель навмисно нудна. У продакшні вам потрібні саме нудні моделі. Моделі для конференцій — для людей без пожежних телефонів.

Практичні завдання: команди, виводи та рішення (12+)

Ось завдання, які я виконую послідовно при діагностиці «handshake did not complete». Кожне містить те, що очікувати, які дивні виводи означають і яке рішення приймати далі.

Завдання 1: Підтвердіть, що WireGuard дійсно працює і на якому порті слухає

cr0x@server:~$ sudo wg show
interface: wg0
  public key: 8x3u...redacted...
  listening port: 51820

peer: 7p1Q...redacted...
  endpoint: (none)
  allowed ips: 10.10.0.2/32
  latest handshake: (none)
  transfer: 0 B received, 0 B sent

Що це означає: у вас є порт, що слухає (добре). «endpoint: (none)» — нормальне явище для серверів; WireGuard динамічно вивчає endpoint клієнта.

Рішення: якщо порт, що слухає, відрізняється від конфігурованого — ви налагоджуєте не той порт. Виправте конфіг і продовжуйте.

Завдання 2: Перевірте, чи OS слухає UDP як очікується

cr0x@server:~$ sudo ss -lunp | grep 51820
UNCONN 0      0            0.0.0.0:51820      0.0.0.0:*    users:(("wg",pid=1132,fd=6))

Що це означає: ядро прив’язано на 0.0.0.0:51820, тож воно повинно приймати пакети на будь-яку локальну адресу.

Рішення: якщо ви цього не бачите — WireGuard не запущено (або прив’язано до іншої адреси). Спочатку виправте сервіс, не чіпайте NAT.

Завдання 3: Перевірте endpoint у конфігурації клієнта (ви будете здивовані)

cr0x@client:~$ sudo wg show
interface: wg0
  public key: dE5...redacted...
  listening port: 48712

peer: 8x3u...redacted...
  endpoint: 203.0.113.10:51820
  allowed ips: 10.10.0.0/24
  latest handshake: (none)
  transfer: 0 B received, 0 B sent
  persistent keepalive: 25

Що це означає: клієнт вважає, що сервер знаходиться за адресою 203.0.113.10:51820. Keepalive встановлено (добре для NAT).

Рішення: якщо IP endpoint неправильний (старий публічний IP, опечатка, неправильний DNS) — виправте і перевірте знову до подальших кроків.

Завдання 4: Доведіть, що сервер отримує будь-які UDP-пакети на порт WireGuard

cr0x@server:~$ sudo tcpdump -ni eth0 udp port 51820
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), snapshot length 262144 bytes

Тепер підніміть тунель на клієнті або згенеруйте трафік.

cr0x@client:~$ sudo wg-quick up wg0
[#] ip link add wg0 type wireguard
[#] wg setconf wg0 /dev/fd/63
[#] ip address add 10.10.0.2/32 dev wg0
[#] ip link set mtu 1420 up dev wg0
[#] ip route add 10.10.0.0/24 dev wg0
[#] resolvconf -a wg0 -m 0 -x

Якщо пакети доходять, tcpdump має показати щось на кшталт:

cr0x@server:~$ sudo tcpdump -ni eth0 udp port 51820
12:22:31.104512 IP 198.51.100.23.48712 > 203.0.113.10.51820: UDP, length 148
12:22:31.104889 IP 203.0.113.10.51820 > 198.51.100.23.48712: UDP, length 92

Що це означає: пакети доходять до сервера, і сервер відповідає. Якщо handshake досі не завершується — ви у зоні «криптографічна ідентичність / AllowedIPs / проблеми з часом».

Рішення:

  • Жодного вхідного рядка: проблема вище по стеку (NAT/брандмауер/ISP/порт-форвард). Рухайтесь туди далі.
  • Вхідні, але немає вихідної відповіді: блокування вихідних на сервері або політика маршрутизації блокує відповіді.
  • Обидва напрямки присутні: переходьте до перевірки ключів/часу/AllowedIPs.

Завдання 5: Перевірте правила iptables/nftables, що можуть скидати UDP/51820

cr0x@server:~$ sudo nft list ruleset | sed -n '1,120p'
table inet filter {
  chain input {
    type filter hook input priority 0; policy drop;
    ct state established,related accept
    iif "lo" accept
    tcp dport 22 accept
    udp dport 51820 accept
    counter drop
  }
}

Що це означає: UDP 51820 явно дозволений. Добре.

Рішення: якщо ви не бачите правила allow (або політика — drop), додайте його. Якщо політика accept, але tcpdump все одно порожній — брандмауер не ваша проблема.

Завдання 6: Перевірте пересилання IP, якщо це site-to-site або ви очікуєте доступу до LAN

cr0x@server:~$ sysctl net.ipv4.ip_forward
net.ipv4.ip_forward = 0

Що це означає: форвардинг вимкнений. Handshake все одно може працювати, але маршрутизований трафік не пройде.

Рішення: якщо потрібно маршрутизувати між wg0 і іншим інтерфейсом — ввімкніть forwarding і налаштуйте брандмауер/NAT відповідно.

Завдання 7: Перевірте rp_filter (маленький каральник асиметричної маршрутизації)

cr0x@server:~$ sysctl net.ipv4.conf.all.rp_filter net.ipv4.conf.eth0.rp_filter
net.ipv4.conf.all.rp_filter = 1
net.ipv4.conf.eth0.rp_filter = 1

Що це означає: увімкнено строгий reverse path filtering. Якщо відповіді йдуть іншим інтерфейсом, ніж ядро очікує, пакети можуть відкидатися.

Рішення: у мультигомованих або при політично-направленій маршрутизації встановіть rp_filter в 2 (loose) або підлаштуйте маршрути так, щоб шлях повернення збігався. Не вимикайте його на зовнішніх хостах без розуміння наслідків.

Завдання 8: Підтвердіть, що сервер має правильний публічний IP і маршрут назад до клієнта

cr0x@server:~$ ip route get 198.51.100.23
198.51.100.23 via 203.0.113.1 dev eth0 src 203.0.113.10 uid 0
    cache

Що це означає: сервер відповість через eth0 використовуючи 203.0.113.10. Добре.

Рішення: якщо маршрут вказує кудись несподівано — ви маєте справу з асиметричною маршрутизацією. Виправляйте маршрути перед тим, як чіпати конфігурації WireGuard.

Завдання 9: Підтвердіть, що ключі збігаються (не висливаючи секрети)

cr0x@server:~$ sudo wg show wg0 public-key
8x3u...redacted...
cr0x@client:~$ sudo wg show wg0 peers
8x3u...redacted...

Що це означає: клієнт націлений на публічний ключ сервера.

Рішення: якщо ключі не співпадають — handshake ніколи не завершиться. Виправте ключі, перезапустіть інтерфейс, повторіть tcpdump.

Завдання 10: Перевірте системний час і синхронізацію NTP на обох кінцях

cr0x@server:~$ timedatectl
               Local time: Sat 2025-12-27 12:26:02 UTC
           Universal time: Sat 2025-12-27 12:26:02 UTC
                 RTC time: Sat 2025-12-27 12:26:01
                Time zone: Etc/UTC (UTC, +0000)
System clock synchronized: yes
              NTP service: active
          RTC in local TZ: no

Що це означає: годинник сервера в межах норми і синхронізований.

Рішення: якщо одна зі сторін не синхронізована і час сильно відрізняється — виправте NTP перед тим, як гнатися за привидами.

Завдання 11: Спостерігайте переходи станів handshake і лічильники в реальному часі

cr0x@client:~$ watch -n 1 sudo wg show wg0
Every 1.0s: sudo wg show wg0

interface: wg0
  public key: dE5...redacted...
  listening port: 48712

peer: 8x3u...redacted...
  endpoint: 203.0.113.10:51820
  allowed ips: 10.10.0.0/24
  latest handshake: 4 seconds ago
  transfer: 3.12 KiB received, 2.98 KiB sent
  persistent keepalive: 25

Що це означає: handshake тепер завершується; лічильники рухаються.

Рішення: якщо мітка handshake оновлюється, але ваші додатки досі падають — перестаньте звинувачувати handshake. Рухаємось до маршрутизації/AllowedIPs/MTU/DNS.

Завдання 12: Валідуйте AllowedIPs і рішення маршрутизації за допомогою ip route get

cr0x@client:~$ ip route get 10.10.0.1
10.10.0.1 dev wg0 src 10.10.0.2 uid 1000
    cache

Що це означає: трафік до 10.10.0.1 піде в wg0.

Рішення: якщо маршрут веде через ваш дефолтний інтерфейс — AllowedIPs/маршрути не встановлені так, як ви думаєте.

Завдання 13: Виявлення MTU black hole за допомогою DF-ping

cr0x@client:~$ ping -M do -s 1380 -c 3 10.10.0.1
PING 10.10.0.1 (10.10.0.1) 1380(1408) bytes of data.
1388 bytes from 10.10.0.1: icmp_seq=1 ttl=64 time=32.1 ms
1388 bytes from 10.10.0.1: icmp_seq=2 ttl=64 time=31.8 ms
1388 bytes from 10.10.0.1: icmp_seq=3 ttl=64 time=32.0 ms

--- 10.10.0.1 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2003ms

Що це означає: принаймні 1380-байтові payload-и проходять; MTU, ймовірно, в нормі.

Рішення: якщо ви отримуєте «Frag needed and DF set» або мовчазні втрати при більших розмірах — відрегулюйте MTU (звичайно 1420, 1380 або менше залежно від інкапсуляції й шляху).

Завдання 14: Доведіть, чи сервер знаходиться за CGNAT (порт-форвард не допоможе)

cr0x@server:~$ ip -4 addr show dev eth0 | sed -n '1,5p'
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
    inet 100.64.12.34/24 brd 100.64.12.255 scope global eth0
       valid_lft forever preferred_lft forever

Що це означає: 100.64.0.0/10 — це діапазон carrier-grade NAT. Ваш «сервер» не має реальної публічної IPv4 на інтерфейсі.

Рішення: припиніть намагатися порт-форвардити на цій машині; потрібен публічний endpoint в іншому місці (VPS, IPv6, relay-дизайн або інший тариф у провайдера).

Завдання 15: Підтвердіть, що сервер бачить IP/порт клієнта стабільно (переприв’язка NAT)

cr0x@server:~$ sudo wg show wg0 | sed -n '1,30p'
interface: wg0
  public key: 8x3u...redacted...
  listening port: 51820

peer: 7p1Q...redacted...
  endpoint: 198.51.100.23:48712
  allowed ips: 10.10.0.2/32
  latest handshake: 12 seconds ago
  transfer: 14.21 KiB received, 13.88 KiB sent

Що це означає: WireGuard вивчив endpoint. Якщо endpoint постійно змінюється щохвилини — NAT-пристрій переприв’язує часто.

Рішення: встановіть PersistentKeepalive = 25 на клієнті (або ту сторону, що за NAT) і розгляньте використання стабільнішого upstream.

NAT і порт-форвардинг: як це ламається в реальних мережах

NAT — причина №1, чому handshakes не завершуються, і це далеко не статистика. Це тому, що «endpoint» — рухома ціль, коли якась сторона знаходиться за пристроєм, що:

  • непередбачувано переписує вихідні порти,
  • швидко вичерпує UDP-мапінги,
  • не підтримує «hairpin NAT» (NAT loopback),
  • або взагалі не той NAT, який ви думали налаштовувати (double NAT).

Знайте, яка сторона має бути досяжною

WireGuard може працювати в кількох режимах, але найпростіший — сервер має публічну IP і відкритий UDP-порт, клієнти підключаються назовні. Це «стандартна інтернет-конфігурація».

Якщо обидві сторони за NAT і жодна не має вхідного порт-мапінгу — ви намагаєтесь виконати NAT-треверсал без посередника. Іноді це працює випадково. Продакшн — місце, де випадковості помирають.

Double NAT: прихований другий маршрутизатор

Double NAT звичайне явище: модем/роутер провайдера робить NAT, потім ваш фаєрвол робить NAT ще раз. Ви форвардите UDP/51820 на внутрішньому роутері, тішитесь, і нічого не працює, бо зовнішній роутер скидає пакети.

Як швидко помітити:

  • WAN IP вашого роутера належить до приватного діапазону (192.168/10.0/172.16) або CGNAT (100.64/10).
  • tcpdump на сервері нічого не бачить, хоча ви впевнені, що порт форварджено.

Таймаути NAT: чому працює 30 секунд, а потім вмирає

UDP «з’єднання» — ілюзія NAT. Коли клієнт відправляє UDP назовні, NAT створює мапінг (src IP:src port → public IP:public port). Цей мапінг закінчується при простої. Якщо він закінчився — відповідь сервера йде в порожнечу. WireGuard може відновитися, але тільки коли новий трафік спричиняє новий handshake. Користувачі називають це «випадкові розриви». SRE називають це «передбачувано проста сесія».

Виправлення зазвичай — PersistentKeepalive на стороні, що за NAT. Не на публічному сервері. На тій стороні, якій потрібна дірка в NAT.

Порт-форвардинг: три правила, які ігнорують

  1. Форвардьте UDP, а не TCP. Бачив, як шаблон для заявки за замовчуванням ставив TCP/UDP, але насправді лишав тільки TCP. Поганий день.
  2. Форвардьте на правильний внутрішній хост IP. Зміни DHCP — рецепт для періодичних відмов і звинувачення інтернету.
  3. Не форвардьте на хоста, який вже запускає іншу UDP-службу на тому ж порту. Звучить очевидно; менш очевидно при копіюванні конфігів для кількох тунелів.

Брандмауери й фільтрація UDP: довести негатив

Брандмауери — друга за поширеністю причина, бо відмови UDP не дають ввічливу помилку. Вони дають тишу, яку ваш мозок трактує як містерію.

Використовуйте tcpdump як сироватку правди

Якщо сервер ніколи не бачить пакети на UDP/51820 — припиніть редагувати WireGuard-конфіги. У вас проблема досяжності мережі. Правильний інструмент — захоплення пакетів. Все інше — світлове шоу.

Stateful-брандмауери і міфи «related/established»

Багато політик брандмауера дозволяють вхідні пакети лише якщо вони відповідають встановленому з’єднанню. UDP — «безз’єднанний», але stateful-брандмауери все одно відстежують потоки з таймаутами. Ось чому keepalive допомагає, і саме тому зворотний трафік інколи відкидають, якщо брандмауер не бачив ініціації в очікуваному напрямку.

Cloud security groups vs host firewall

У хмарі часто є кілька рівнів контролю:

  • security group / network ACL провайдера,
  • локальний брандмауер на хості (nftables/iptables/ufw/firewalld),
  • іноді керований load balancer, який не любить UDP, якщо явно не налаштований.

Визначте одне джерело істини й документуйте його. Якщо «просто дозволити всюди» — ви зламаєте потім, але вже з більшою зоною ураження.

Жарт 2/2: Дебаг UDP через три брандмауери — як офісна політика: ніхто не признається, що скинув ваш пакет, але всі мали «політичну причину».

Маршрутизація й AllowedIPs: тихий підводний камінь

WireGuard AllowedIPs — не декоративна опція. Вона виконує дві ролі:

  1. Фільтрація вхідного трафіку: які source IP ви приймаєте від цього peer.
  2. Маршрутизація вихідного трафіку: які destination IP шифруються й відправляються до цього peer.

Це елегантно. Але саме через це одна помилкова CIDR може робити трафік невидимим без помилок handshake. Іноді handshake проходить, але ви нічого не бачите через відсутність маршрутів у тунелі. Інколи handshake не проходить, бо IP тунельного пірину не вважається дійсним для цього peer.

Типові патерни AllowedIPs

  • Road warrior client: сервер ставить AllowedIPs = 10.10.0.2/32 для цього клієнта. Клієнт ставить AllowedIPs = 0.0.0.0/0, ::/0 якщо потрібно full-tunnel, або лише приватні діапазони при split-tunnel.
  • Site-to-site: кожна сторона рекламує свої локальні LAN через AllowedIPs, щоб інша сторона могла маршрутизувати до них. Тут виникають конфлікти та накладення RFC1918.

Перекриття підмереж: корпоративна класика

Якщо обидві сторони використовують 192.168.1.0/24 локально, ви можете встановити тунель, але маршрутизація стає лотереєю. Люди «вирішують» це додаванням статичних маршрутів, аж доки мережа не починає нагадувати стіну з теорією змови. Не робіть так. Перенумеруйте одну сторону або використайте NAT у тунелі навмисно і задокументуйте це.

Час і захист від повторів: коли годинники псують ваш день

Час рідко викликає відмови handshake, але коли це трапляється — це дратує, бо відчувається як магія. WireGuard включає захист від повторів: він не приймає пакети, що схожі на повтори. Якщо годинники серйозно розбігаються або VM призупинена/відновлена з перестрибуванням часу, ви можете отримати симптоми, що виглядають як «рукопотискання просто не відбувається», особливо після відновлень або зі снэпшотами.

Що робити на практиці:

  • Зробіть NTP нудним. Використовуйте systemd-timesyncd/chronyd. Переконайтеся, що сервіс стартує рано при завантаженні.
  • На віртуалізованих хостах перевірте, щоб синхронізація гіпервізора не суперечила NTP.
  • Якщо ви використовуєте снапшоти і відновлення — очікуйте дивакуватості: відновлена VM може мати старий стан WireGuard і старий час.

Як визначити, що час — проблема

Зазвичай ви бачите:

  • handshakes, які проходять одразу після перезавантаження або синхронізації часу, а потім знову відмовляють,
  • або відмови після паузи/відновлення VM,
  • або системний годинник явно неправильний (timedatectl показує несинхронізований стан).

MTU і фрагментація: повільніший родич handshakes

Чітко кажучи, проблеми з MTU частіше ламають саме дані, а не handshake, бо handshake-пакети невеликі. Але на практиці люди діагностують “handshake did not complete”, бо тунель «не працює», і вони ніколи не відділяють контрольну площину від площини даних.

Проблеми MTU виявляються як:

  • handshake працює, але великі передачі зависають,
  • деякі сайти працюють, інші — ні (класичний PMTUD black hole),
  • SSH працює, HTTPS затримується на великих відповідях,
  • VoIP — нестабільний і перекручують кодек.

Виправляйте по-дорослому

Почніть із стандартного MTU від wg-quick (зазвичай 1420). Якщо є додаткова інкапсуляція (PPPoE, VLAN, інші тунелі) — зменшіть його. Перевірте DF-ping, потім переходьте до MSS-clamping, якщо трафік переважно TCP.

Три корпоративні історії з бойових умов

Інцидент 1: відмова через неправильне припущення

Середня компанія розгорнула WireGuard як «простий віддалений VPN» для інженерів. У дизайн-документі було: «Відкрити UDP/51820 на VPN-сервері». Все просто.

Вони розмістили сервер у колокації за фронтовим фаєрволом, яким керувала окрема мережева команда. Команда застосунку попросила «відкрити порт 51820». Мережна команда відкрила TCP/51820, бо шаблон їхньої системи заявок за замовчуванням ставив TCP, і ніхто цього не перевірив. Всі тестували з офісної мережі, де внутрішній NAT hairpin іноді робив вигляд, що все працює — найгірший вид «іноді працює».

У день запуску віддалені користувачі побачили «Handshake did not complete». Команда витратила години на ротацію ключів і перебудову конфігів. Тим часом мережева команда наполягала: «порт відкритий». Той був. Просто не той протокол.

Виправлення — однорядкова зміна в брандмауері, дозволити UDP/51820 inbound. Урок був не стільки про WireGuard, скільки про припущення: ніколи не приймайте «порт відкритий» без «протокол відкритий», і не налагоджуйте криптографію, поки не підтвердили, що пакети приходять.

Інцидент 2: оптимізація, що вдарила по продуктивності

Інша організація хотіла «зменшити фоновий шум» на мобільних клієнтах, щоб зекономити батарею й трафік. Хтось помітив PersistentKeepalive = 25 і вирішив, що це марнотратно. Його видалили по всьому флоту.

В офісі Wi‑Fi і корпоративних LTE-планах це працювало. Потім польові співробітники почали використовувати випадкові мережі: готельний Wi‑Fi, кав’ярні, аеропорти. Ці мережі часто мали агресивні таймаути UDP. Тунелі вмирали мовчки. Коли користувачі знову починали працювати, іноді генерувався новий handshake, але не завжди в потрібному порядку для їхніх додатків. Вони бачили переривчасті відмови: іноді після 10–30 секунд це працювало, іноді доводилось перезапускати додаток.

Інцидент-реакція зосередилась на версіях WireGuard, ядрах і «можливо, криптографія зламалась». Ні. Оптимізація забрала єдиний механізм тримання NAT-мапінгів відкритими.

Їм довелося повернути keepalive — вибірково. Лаптопи завжди отримали keepalive. Телефони — з довшим інтервалом і лише для профілів, що використовуються в «ворожих» мережах. Головний урок: оптимізація без моделі відмов — це добровільна підписка на майбутні інциденти.

Інцидент 3: нудна, але правильна практика врятувала день

Команда фінансових послуг мала звичку, яка здавалася старомодною: кожна зміна мережевого сервісу супроводжувалася захопленням пакетів на обох кінцях протягом тестового вікна, збереженим разом з записом зміни. Не тому, що цього вимагала відповідність, а тому що вони любили спати.

Одної п’ятниці невелика зміна маршрутизації у провайдера перемістила публічну IP сервера WireGuard (планово). DNS оновився швидко, але частина клієнтів і далі зверталась до старого IP через кеші та застарілі резолвери. Користувачі бачили «Handshake did not complete». На чергуванні команда за кілька хвилин порівняла останнє відоме захоплення пакетів і нове захоплення.

Каптури розповіли історію: клієнти відправляли UDP на старий IP; сервер цього не бачив. Нема потреби чіпати ключі, MTU чи правила фаєрволу. Вони тимчасово зменшили TTL DNS для запису на час міграції і прислали оновлений IP тим клієнтам, що не могли покладатися на DNS.

Це було нудно, вимірювано і швидко. Практика не була гламурною, але вона перетворила здогадки на короткий інцидент.

Типові помилки: симптом → корінна причина → виправлення

  • Симптом: Клієнт показує «latest handshake: (none)»; серверний tcpdump не показує вхідних UDP.
    Корінна причина: неправильний публічний IP/endpoint, upstream-фаєрвол, відсутній порт-форвард, CGNAT або блокування UDP провайдером.
    Виправлення: перевірте endpoint IP, відкрийте UDP на всіх рівнях брандмауера, налаштуйте правильний порт-форвард або перемістіть сервер на реальний публічний IP (або використайте IPv6).
  • Симптом: Сервер бачить вхідні UDP-пакети, але ніколи не відправляє відповіді.
    Корінна причина: локальний брандмауер блокує output, політична маршрутизація відправляє відповіді іншим інтерфейсом, rp_filter відкидає, або WireGuard не прив’язаний/не запущений правильно.
    Виправлення: дозволити вихідний UDP, виправити маршрути, послабити rp_filter за потреби, перевірити ss -lunp і wg show.
  • Симптом: Сервер відповідає в tcpdump; клієнт ніколи не бачить відповіді.
    Корінна причина: клієнтський брандмауер, NAT-мапінг вичерпався, симетричний NAT або блокування шляху назад.
    Виправлення: додати PersistentKeepalive на клієнті, дозволити вхідний UDP з сервера, протестувати з іншої мережі або використати сервер зі стабільним підключенням.
  • Симптом: Handshake проходить, але ви нічого не можете досягти через тунель.
    Корінна причина: неправильні AllowedIPs, відсутні маршрути, IP forwarding вимкнено, відсутні правила NAT/forward на сервері або перекриття підмереж.
    Виправлення: виправити AllowedIPs, перевірити ip route get, ввімкнути forwarding, додати правила forward/NAT, перенумерувати або використовувати NAT на одній стороні.
  • Симптом: Працює хвилину, потім вмирає при простої; мітка handshake перестає оновлюватися.
    Корінна причина: таймаут UDP NAT; мапінг вичерпався.
    Виправлення: PersistentKeepalive = 25 (або інше значення) на peer за NAT; якщо ви контролюєте фаєрвол — збільшіть таймаут сесії UDP для цього порту/хоста.
  • Симптом: Працює в деяких мережах, ніколи в інших (особливо гостеві корпоративні мережі).
    Корінна причина: UDP блокується або лімітується; дозволений лише TCP/443.
    Виправлення: забезпечити альтернативний egress (інша мережа) або розгорнути дизайн, що не вимагає сирого UDP-egress для цих клієнтів (організаційне рішення, не опція WireGuard).
  • Симптом: Після відновлення VM або паузи handshakes поводяться непередбачувано.
    Корінна причина: стрибок часу/дрейф часу, застарілий стан або випадки захисту від повторів.
    Виправлення: забезпечити синхронізацію часу, перезавантажити/опустити інтерфейс і не використовувати старі снапшоти як «швидке» виправлення для мережевих сервісів.
  • Симптом: Handshake працює, малі ping-и проходять, великі завантаження зависають.
    Корінна причина: MTU/PMTUD black hole; фрагментація заблокована.
    Виправлення: знизити WG MTU, тестувати DF-ping, здійснити clamp TCP MSS на маршрутизуючих шляхах.

Контрольні списки / поетапний план

Контрольний список A: «Handshake ніколи не завершується» (жорстка відмова)

  1. Підтвердьте endpoint: IP і порт у клієнтському Endpoint правильні.
  2. Підтвердьте, що сервер слухає: wg show і ss -lunp.
  3. Захоплення пакетів на сервері: tcpdump -ni <if> udp port <port>.
  4. Якщо сервер нічого не бачить: перевірте порт-форварди, зовнішні фаєрволи і чи має сервер реальний публічний IP (не 100.64/10).
  5. Якщо сервер бачить лише вхід: перевірте egress брандмауер сервера, маршрути та rp_filter.
  6. Якщо сервер бачить обидва напрямки: перевірте ключі і синхронізацію часу; потім перевірте, чи якийсь middlebox не переписує або не блокувує відповіді на боці клієнта.

Контрольний список B: «Handshake завершується, але трафік не йде» (м’яка відмова)

  1. Підтвердьте рух лічильників: wg show має показувати зростання байтів.
  2. Перевірте AllowedIPs: переконайтеся, що мережі призначення включені на стороні відправника; переконайтеся, що IP тунелю peer-ів правильні на стороні отримувача.
  3. Перевірте маршрути: ip route get <dest> має вказувати у wg0 для тунельованих адрес.
  4. Перевірте форвардинг/NAT: якщо очікується доступ до LAN — увімкніть forwarding і дозвольте трафік у forward-ланцюзі.
  5. Тест MTU: DF-ping з ростучими розмірами; відрегулюйте WG MTU або clamp MSS.
  6. Потім DNS: лише після того, як IP-з’єднання працює. Проблеми з DNS маскуються під «VPN не працює» і забирають години.

Контрольний список C: «Відвалюється кожні кілька хвилин» (переривчаста відмова)

  1. Спостерігайте зміни endpoint: поле endpoint на сервері в wg show флапає — це вказує на переприв’язку NAT.
  2. Додайте keepalive: PersistentKeepalive = 25 на стороні за NAT; підлаштуйте інтервал за потреби.
  3. Перевірте UDP-таймаути: на фаєрволах, які ви контролюєте, збільште UDP session timeout для цього порту/хоста.
  4. Шукайте конкуренцію NAT-пристроїв: double NAT і «client isolation» гостьових Wi‑Fi можуть поводитись як втрата пакетів.

FAQ

1) Чи завжди «Handshake did not complete» означає, що UDP-порт заблоковано?

Ні. Це означає, що пакети handshake не були успішно обмінені. Блокування UDP — поширена причина, але також може бути неправильний endpoint IP, асиметрична маршрутизація або несумісність ключів. Використовуйте tcpdump, щоб відокремити «пакети не приходять» від «пакети приходять, але handshake не вдається».

2) Якщо я можу пропінгувати публічний IP сервера, чому WireGuard не рукопотискається?

Ping — це ICMP. WireGuard використовує UDP. Мережі часто дозволяють ICMP, але блокують або лімітують UDP, особливо в корпоративних гостевих мережах та у деяких ISP. Тестуйте доступність UDP за допомогою захоплень пакетів, а не інтуїції.

3) Чи слід змінювати порт WireGuard з 51820?

Іноді. Якщо ви підозрюєте фільтрацію на звичних VPN-портах, перехід на випадковий високий UDP-порт може допомогти. Але не робіть це автоматично: якщо tcpdump на сервері нічого не показує, вам все одно потрібно відкрити/форвардити новий порт на всіх рівнях.

4) Де ставити PersistentKeepalive?

На peer-і, що знаходиться за NAT і має залишатися доступним. Зазвичай це клієнт. Встановлення його на публічному сервері нічого не дасть для NAT-мапінгів клієнта.

5) Чи може неправильний AllowedIPs завадити handshake?

Так, у кількох випадках. Якщо сервер очікує, що peer використовуватиме певний тунельний IP, але AllowedIPs не включає його, пакети можуть відкидатися як «не від цього peer». Частіше handshake проходитиме, але дані «ніде не йдуть», бо маршрути не направляють пакети у тунель.

6) Чому працює на роздачі з телефону, але не в офісі?

Офісні мережі часто блокують вихідний UDP, крім DNS/NTP, або запускають агресивну stateful-інспекцію, яка вбиває довгоживучі потоки. Роздачі з телефону зазвичай простіші й лояльніші до UDP.

7) Чи потрібно перезапускати WireGuard після зміни правил фаєрволу?

Ні, зміни брандмауера застосовуються одразу. Перезапуск може допомогти прибрати плутанину, але не є обов’язковим. Краще змінювати одну змінну за раз, щоб знати, що саме виправило проблему.

8) Як дізнатися, чи я за CGNAT?

Якщо WAN-інтерфейс має IP в діапазоні 100.64.0.0/10 — ви за CGNAT. Також, якщо WAN IP вашого роутера — приватний (192.168/10.0/172.16), у вас upstream NAT. Вхідний порт-форвард не працюватиме, якщо ви не контролюєте upstream NAT.

9) Handshake проходить, але лише деякі підмережі доступні. Чому?

Зазвичай це невідповідність маршрутизації/AllowedIPs або перекриття підмереж. Перевірте ip route get для кожного призначення і впевніться, що AllowedIPs кожного peer включає потрібні CIDR.

10) Чи справді синхронізація часу важлива для WireGuard?

У більшості випадків — ні. Але коли годинники дуже відрізняються — особливо після паузи VM або відновлення зі снапшоту — захист від повторів і стан можуть поводитися так, ніби handshake випадково зламався. Тримайте NTP робочим і нудним.

Висновок: наступні кроки, які справді зменшують відладку

Коли WireGuard каже «Handshake did not complete», це не заклик до медитації над криптографією. Це заклик йти за пакетами. Виконайте нудну послідовність:

  1. Перевірте endpoint/порт і що сервер слухає.
  2. Захопіть пакети на сервері: чи приходять вхідні UDP-пакети?
  3. Якщо так — чи виходять відповіді і чи отримує їх клієнт?
  4. Якщо handshakes проходять — перестаньте дивитись на handshakes і виправляйте маршрути/AllowedIPs/форвардинг/MTU.

Оперативно, ваш найкращий довгостроковий виграш — стандартизувати мінімальний набір діагностики: вивід wg show, ss -lunp, 30-секундний tcpdump на обох кінцях і timedatectl. Тренуйте це, і ваше майбутнє «я» подякує вам — тихо, бо нарешті спатиме.

← Попередня
Квоти ZFS для мультиорендарів: як не допустити, щоб один користувач вбив пул
Наступна →
Розмір L2ARC у ZFS: коли 200 ГБ кращі за 2 ТБ

Залишити коментар