WireGuard est généralement ennuyeux — au sens positif. Jusqu’à ce qu’il ne le soit pas. Vous déployez un tunnel, l’interface remonte, puis le statut reste là comme un poisson mort : latest handshake: (none) ou « Handshake did not complete. » Pendant ce temps votre téléphone d’astreinte invente de nouvelles façons de vibrer.
Cette panne est sournoisement simple : un côté ne parvient pas à compléter la danse cryptographique avec l’autre. Le piège est que la cause racine n’est souvent pas du tout la cryptographie. C’est un état NAT qui expire, un port UDP bloqué en silence, une route pointant vers le vide, une fragmentation MTU qui transforme les paquets en confettis, ou une dérive d’horloge qui fait que la protection contre la répétition fait un peu trop bien son travail.
Ce que « handshake » signifie vraiment (et ce que ce n’est pas)
Le handshake de WireGuard est un petit échange rapide de paquets UDP qui établit des clés de session éphémères. S’il se termine, vous verrez une date/heure « latest handshake » et des octets circuler. S’il ne se termine pas, l’interface peut quand même apparaître « up » parce que ce n’est qu’une interface ; WireGuard ne fait pas de « connected » au sens TCP.
Cette dernière phrase importe parce qu’un tas d’erreurs de diagnostic se produisent ici. Les gens voient wg0 exister et supposent la connectivité. WireGuard ne promet rien ; il offre une opportunité.
De plus : « handshake did not complete » n’est pas un seul bug. C’est une classe de pannes :
- Les paquets ne quittent jamais le client (pare-feu local, table de routage erronée, nom d’hôte endpoint incorrect).
- Les paquets partent mais n’arrivent pas (ISP bloque UDP, pare-feu en amont, redirection de port mal configurée, CGNAT).
- Les paquets arrivent mais la réponse ne peut pas revenir (routage asymétrique, désaccord sur le mapping NAT, rp_filter, pare-feu egress).
- Le handshake se termine mais le trafic échoue (AllowedIPs, routage, MTU, DNS). Celui-ci ressemble au précédent si vous ne regardez que le « ping ».
- Le handshake échoue de manière intermittente (timeout NAT, changement d’endpoint en roaming, absence de keepalive).
- Le handshake est rejeté (clés incorrectes, peer obsolète, problèmes de temps/protection contre la répétition). Moins fréquent, mais ça arrive.
Une citation, parce que c’est toujours le meilleur conseil opérationnel dans ce domaine : L’espoir n’est pas une stratégie.
— Gene Kranz. Vous allez mesurer, pas deviner.
Plan de diagnostic rapide (à vérifier en 1er/2e/3e)
Si vous ne faites qu’une chose de cet article, faites ceci dans l’ordre. Ça réduira votre temps de dépannage de « ruiner ma soirée » à « légèrement agaçant ».
Premier : prouver la reachabilité UDP vers le port serveur
- Sur le serveur, vérifiez qu’il écoute sur le port attendu (
wg showetss -lunp). - Sur le serveur, lancez
tcpdumpsur l’interface publique pour ce port UDP. - Depuis le client, initiez du trafic (même un seul ping via le tunnel, ou simplement remontez l’interface).
- Si tcpdump serveur ne voit rien : le problème est en amont de WireGuard (NAT/redirection de port/pare-feu/ISP/CGNAT/IP incorrecte).
Second : si les paquets arrivent, prouver que le serveur répond et que le client reçoit les réponses
- Laissez tcpdump sur le serveur : voyez-vous des UDP sortants vers le client ?
- Exécutez tcpdump sur le client : voyez-vous des UDP entrants depuis le serveur ?
- Si le serveur répond mais que le client ne voit jamais la réponse : NAT intermédiaire, chemin de retour, ou drop par un pare-feu stateful.
Troisième : si les handshakes ont lieu mais que le trafic échoue, déboguez AllowedIPs, routes et MTU
wg showdoit montrer un handshake récent et des compteurs en hausse.ip route getpour des IP de destination doit pointer verswg0quand c’est approprié.- Ping avec DF activé (ou MSS clamp) pour repérer les trous MTU.
Voilà. Vous cherchez le premier maillon brisé de la chaîne : « le client émet UDP → le serveur reçoit UDP → le serveur répond → le client reçoit la réponse → les clés sont établies → les routes envoient le trafic dans le tunnel → le trafic survit au MTU. »
Faits et contexte utiles pour débattre avec des collègues
- WireGuard a été conçu pour être minimal. La base de code est réputée petite comparée aux piles VPN anciennes, ce qui réduit les « inconnues » lors des incidents.
- Il utilise UDP par conception. Cela évite le effondrement TCP-over-TCP et se comporte bien en roaming, mais cela vous oblige à penser en termes de reachabilité et d’état NAT.
- Le handshake est basé sur Noise. La famille de protocoles est une approche moderne d’échange de clés authentifié visant la simplicité et des propriétés robustes.
- « AllowedIPs » est à la fois ACL et politique de routage. C’est un choix de conception délibéré : puissant et souvent source d’erreurs.
- WireGuard ne surveille pas la liveness comme on l’attend. Il n’y a pas d’état « connecté » ; il envoie une initiation de handshake quand il a du trafic à envoyer.
- PersistentKeepalive existe principalement pour le NAT. Si vous vous êtes déjà énervé contre un Wi‑Fi d’hôtel, cette option est la manière polie de WireGuard de taper la table NAT toutes les N secondes.
- Beaucoup de routeurs grand public ont des timeouts UDP courts. Il n’est pas rare de voir des mappings expirer en 30–120 secondes d’inactivité.
- Le Carrier-grade NAT est courant aujourd’hui. Beaucoup de connexions « publiques » ne sont pas réellement publiques pour l’inbound. Aucun port forward ne pourra le réparer.
- La protection contre la répétition dépend du temps. Si les horloges sont fortement déréglées, certains paquets légitimes peuvent être traités comme des replays. La sécurité fait son travail ; vous êtes celui qui voyage dans le temps.
Blague 1/2 : le NAT, c’est comme un helpdesk d’entreprise : il oublie que vous existez dès que vous arrêtez d’envoyer des tickets.
Un modèle mental pratique : paquets, états et temps
Pensez au handshake comme à quatre éléments qui doivent être vrais :
- Adressage correct : le client connaît l’endpoint du serveur (IP:port), le serveur sait comment répondre.
- Reachabilité UDP bidirectionnelle : pas seulement « ping fonctionne », mais « les paquets UDP traversent dans les deux sens ».
- L’état tient assez longtemps : NAT et pare-feu maintiennent le mapping/état suffisamment longtemps pour le handshake et le trafic suivant.
- L’identité cryptographique correspond : clés publiques et AllowedIPs s’alignent ; la protection contre la répétition n’est pas déclenchée par des problèmes d’horloge.
Ce modèle est volontairement ennuyeux. Vous voulez des modèles ennuyeux en production. Les modèles fantaisistes sont pour les conférences et pour ceux qui ne portent pas de téléphones d’astreinte.
Tâches pratiques : commandes, sorties et décisions (12+)
Voici les tâches que j’exécute dans l’ordre quand je débogue un « handshake did not complete ». Chacune inclut ce à quoi vous devez vous attendre, ce que signifie une sortie étrange, et la décision suivante à prendre.
Task 1: Confirm WireGuard is actually running and which port it listens on
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
Ce que ça signifie : vous avez un port à l’écoute (bien). « endpoint: (none) » est normal sur les serveurs ; WireGuard apprend dynamiquement les endpoints clients.
Décision : si le port d’écoute n’est pas celui que vous avez configuré, vous dépannez le mauvais port. Corrigez la config d’abord, puis continuez.
Task 2: Verify the OS is listening on UDP as expected
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))
Ce que ça signifie : le noyau est lié sur 0.0.0.0:51820, il devrait donc accepter les paquets pour toute adresse locale.
Décision : si vous ne le voyez pas, WireGuard n’est pas monté (ou lié à une autre adresse). Réparez le service d’abord ; ne touchez pas au NAT encore.
Task 3: Check the endpoint configured on the client (you’d be shocked)
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
Ce que ça signifie : le client pense que le serveur est à 203.0.113.10:51820. Le keepalive est réglé (bon pour le NAT).
Décision : si l’IP endpoint est fausse (ancienne IP publique, faute de frappe, DNS erroné), corrigez-la et retestez avant toute autre action.
Task 4: Prove the server receives any UDP packets on the WireGuard port
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
Maintenant, remontez le tunnel client ou générez du trafic.
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
Si des paquets arrivent, tcpdump devrait montrer quelque chose comme :
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
Ce que ça signifie : les paquets atteignent le serveur et le serveur répond. Si le handshake ne se termine toujours pas, vous êtes en territoire « identité cryptographique / AllowedIPs / problèmes de temps ».
Décision :
- Aucune ligne entrante du tout : c’est en amont (NAT/pare-feu/redirection/ISP/port forward). Allez voir là-bas ensuite.
- Entrant mais pas de réponse sortante : le pare-feu serveur ou le routage politique bloque les réponses.
- Les deux directions présentes : passez aux clés/temps/AllowedIPs.
Task 5: Check iptables/nftables rules that might drop 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
}
}
Ce que ça signifie : UDP 51820 est explicitement autorisé. Parfait.
Décision : si vous ne voyez pas une règle d’autorisation (ou si la politique est drop), ajoutez-la. Si la politique est accept mais que tcpdump ne voit toujours rien, alors le pare-feu n’est pas le problème.
Task 6: Verify IP forwarding if this is a site-to-site or you expect LAN access
cr0x@server:~$ sysctl net.ipv4.ip_forward
net.ipv4.ip_forward = 0
Ce que ça signifie : le forwarding est désactivé. Le handshake peut quand même fonctionner, mais le trafic routé ne passera pas.
Décision : si vous devez router entre wg0 et une autre interface, activez le forwarding et configurez le pare-feu/NAT en conséquence.
Task 7: Check rp_filter (asymmetric routing’s petty enforcer)
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
Ce que ça signifie : le filtrage de chemin inverse strict est activé. Si les réponses sortent par une interface différente de celle attendue par le noyau, les paquets peuvent être rejetés.
Décision : dans des configurations multi‑homées ou de routage politique, mettez rp_filter à 2 (loose) ou ajustez le routage pour que le chemin de retour corresponde. Ne le désactivez pas aveuglément sur des serveurs exposés sans comprendre pourquoi.
Task 8: Confirm the server has the correct public IP and the route back to the client
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
Ce que ça signifie : le serveur répondra via eth0 en utilisant 203.0.113.10. Bien.
Décision : si la route pointe ailleurs que prévu, vous dépannez un routage asymétrique. Corrigez le routage avant de toucher aux configs WireGuard.
Task 9: Confirm keys match what you think they are (without leaking secrets)
cr0x@server:~$ sudo wg show wg0 public-key
8x3u...redacted...
cr0x@client:~$ sudo wg show wg0 peers
8x3u...redacted...
Ce que ça signifie : le client cible la clé publique du serveur.
Décision : si les clés ne correspondent pas, le handshake ne se terminera jamais. Corrigez les clés, redémarrez l’interface, retestez avec tcpdump.
Task 10: Check system time and NTP sync on both ends
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
Ce que ça signifie : l’horloge du serveur est correcte et synchronisée.
Décision : si un côté n’est pas synchronisé et que le temps est fortement décalé, corrigez le NTP avant de chasser des fantômes.
Task 11: Observe handshake state transitions and counters in real time
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
Ce que ça signifie : le handshake se complète maintenant ; les compteurs évoluent.
Décision : si le timestamp du handshake se met à jour mais que vos applications échouent toujours, arrêtez de blâmer le handshake. Passez au routage/AllowedIPs/MTU/DNS.
Task 12: Validate AllowedIPs and routing decisions with 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
Ce que ça signifie : le trafic vers 10.10.0.1 ira dans wg0.
Décision : si ça sort par votre interface par défaut, vos AllowedIPs/routes ne sont pas installés comme vous le pensez.
Task 13: Detect MTU black holes with 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
Ce que ça signifie : au moins des payloads de 1380 octets survivent ; le MTU est probablement OK.
Décision : si vous recevez « Frag needed and DF set » ou une perte silencieuse à des tailles plus grandes, ajustez le MTU (souvent 1420, 1380, ou plus bas selon l’encapsulation et le chemin).
Task 14: Prove the server is or isn’t behind CGNAT (port forwarding won’t save you)
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
Ce que ça signifie : 100.64.0.0/10 est une plage de Carrier-Grade NAT. Votre « serveur » n’a pas d’IPv4 publique réelle sur cette interface.
Décision : arrêtez d’essayer de faire du port forwarding sur cette machine ; vous avez besoin d’un endpoint public ailleurs (VPS, IPv6, design relay, ou un autre forfait ISP).
Task 15: Confirm the server sees the client’s source IP/port consistently (NAT rebinding)
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
Ce que ça signifie : WireGuard a appris l’endpoint. Si l’endpoint continue de changer chaque minute, un appareil NAT rebinde fréquemment.
Décision : mettez PersistentKeepalive = 25 sur le client (ou le côté derrière NAT) et envisagez d’utiliser un upstream stable.
NAT et redirection de port : comment ça casse en vrai
Le NAT est la raison n°1 pour laquelle les handshakes ne se terminent pas, et ce n’est pas proche. C’est parce que l’« endpoint » est une cible mouvante quand l’un des côtés est derrière un appareil qui :
- réécrit les ports source de manière imprévisible,
- expire rapidement les mappings UDP,
- ne supporte pas le « hairpin NAT » (NAT loopback),
- ou n’est pas réellement le NAT que vous devez configurer (double NAT).
Sachez quel côté doit être joignable
WireGuard peut fonctionner en plusieurs schémas, mais le plus simple est : le serveur a une IP publique et un port UDP ouvert, les clients se connectent sortants. C’est la « forme internet par défaut ».
Si les deux côtés sont derrière NAT et qu’aucun n’a de mapping de port entrant, vous essayez une traversée NAT sans médiateur. Parfois ça marche par accident. En production, les accidents meurent.
Double NAT : le routeur caché
Le double NAT est courant : le modem/routeur de l’ISP effectue du NAT, puis votre firewall fait du NAT à nouveau. Vous forwardez UDP/51820 sur le routeur interne, vous êtes fier, et rien ne marche parce que le routeur externe le bloque.
Comment le repérer rapidement :
- L’IP WAN de votre routeur est dans une plage privée (192.168/10.0/172.16) ou CGNAT (100.64/10).
- tcpdump serveur ne voit rien même si vous êtes sûr d’avoir forwardé le port.
Timeouts NAT : pourquoi ça marche 30 secondes, puis ça meurt
Une « connexion » UDP est une illusion NAT. Quand un client envoie un UDP, le NAT crée un mapping (src IP:src port → public IP:public port). Ce mapping expire lorsqu’il est inactif. Si il expire, la réponse du serveur va dans le vide. WireGuard peut récupérer, mais seulement quand un nouveau trafic déclenche un nouveau handshake. Les utilisateurs appellent ça « déconnexions aléatoires ». Les SREs appellent ça « inactivité prévisible ».
La solution est généralement PersistentKeepalive sur le côté NATé. Pas sur le serveur public. Sur le côté qui doit garder un trou ouvert.
Redirection de port : les trois règles que les gens ignorent
- Redirigez UDP, pas TCP. J’ai vu des toggles « TCP/UDP » par défaut passer en TCP-only. Mauvaise journée.
- Redirigez vers la bonne IP hôte interne. Les changements DHCP sont la façon dont vous créez des pannes intermittentes et blâmez « internet ».
- Ne redirigez pas vers un hôte qui exécute déjà un autre service UDP sur ce port. Ça paraît évident ; c’est moins évident quand vous avez plusieurs tunnels et que vous copiez-collez des configs.
Pare-feu et filtrage UDP : prouver une négation
Les pare-feu sont le deuxième coupable le plus fréquent parce que les drops UDP ne vous donnent pas la courtoisie d’une erreur. Ils donnent le silence, que votre cerveau interprète comme mystère.
Utilisez tcpdump comme sérum de vérité
Si le serveur ne voit jamais de paquets sur UDP/51820, arrêtez de modifier les configs WireGuard. Vous avez un problème de reachabilité réseau. L’outil correct est la capture de paquets. Tout le reste est de l’éclairage d’ambiance.
Pare-feu stateful et mythes « related/established »
Beaucoup de politiques de pare‑feu autorisent les paquets entrants uniquement s’ils correspondent à une connexion établie. UDP est « sans connexion », mais les pare‑feu stateful le suivent quand même comme flux avec timeout. C’est pourquoi un keepalive aide, et pourquoi le trafic de retour peut être rejeté si le pare‑feu n’a pas vu l’initiation sortante dans la bonne direction.
Groupes de sécurité cloud vs. pare-feu hôte
Dans le cloud vous avez souvent :
- un groupe de sécurité cloud / ACL réseau,
- un pare-feu hôte (nftables/iptables/ufw/firewalld),
- et parfois un load balancer managé qui n’aime pas UDP sauf s’il est configuré explicitement.
Choisissez une seule source de vérité et documentez-la. Si vous « autorisez partout », vous le casserez quand même plus tard — seulement maintenant vous avez aussi augmenté votre blast radius.
Blague 2/2 : Déboguer de l’UDP à travers trois pare‑feux, c’est comme la politique de bureau — personne n’admet avoir dropé votre paquet, mais tout le monde avait une « raison de politique ».
Routage et AllowedIPs : le briseur de contrat silencieux
Le AllowedIPs de WireGuard n’est pas un paramètre décoratif. Il fait deux jobs :
- Filtrage entrant : quelles adresses source vous accepterez de ce peer.
- Routage sortant : quelles adresses de destination seront chiffrées vers ce peer.
C’est élégant. C’est aussi la raison pour laquelle un seul CIDR erroné peut faire disparaître le trafic sans aucune erreur de handshake. Parfois le handshake se termine bien, mais vous n’atteignez rien parce que rien n’est routé dans le tunnel. D’autres fois, le handshake échoue parce que l’IP du peer dans le tunnel n’est pas considérée comme valide pour ce peer.
Schémas courants AllowedIPs
- Client nomade : le serveur met
AllowedIPs = 10.10.0.2/32pour ce client. Le client metAllowedIPs = 0.0.0.0/0, ::/0si vous voulez full-tunnel, ou juste les plages privées pour split-tunnel. - Site-à-site : chaque côté annonce ses LAN locaux via AllowedIPs afin que l’autre côté puisse router vers eux. C’est là que les chevauchements et collisions RFC1918 deviennent votre ennemi.
Sous‑réseaux qui se chevauchent : le classique corporate
Si les deux côtés utilisent 192.168.1.0/24 en interne, vous pouvez établir un tunnel, mais le routage devient une pièce de hasard. Les gens « corrigent » ensuite en ajoutant des routes statiques jusqu’à ce que le réseau ressemble à un mur de conspiration. Ne faites pas ça. Renumérotez un côté ou utilisez du NAT à l’intérieur du tunnel délibérément et documentez-le.
Temps et protection contre la répétition : quand les horloges ruinent votre journée
Le temps cause rarement des échecs de handshake, mais quand il le fait, c’est exaspérant parce que ça ressemble à de la magie. WireGuard inclut une protection contre la répétition : il n’acceptera pas des paquets qui ressemblent à des replays. Si les horloges sont fortement décalées ou qu’une VM est suspendue/reprise avec des sauts temporels, vous pouvez obtenir des symptômes qui ressemblent à « ça ne veut tout simplement pas handshake », surtout après des réinstallations ou snapshots.
Que faire en pratique :
- Rendez NTP ennuyeux. Utilisez systemd-timesyncd/chronyd. Assurez-vous qu’il démarre tôt au boot.
- Sur hôtes virtualisés, confirmez que la synchronisation horaire de l’hyperviseur ne se bat pas avec NTP.
- Si vous utilisez des snapshots et des restores, attendez-vous à des bizarreries : une VM restaurée peut avoir un état WireGuard ancien et un temps ancien.
Comment savoir si le temps est le problème
Vous verrez généralement :
- des handshakes qui se terminent juste après un reboot ou une synchronisation, puis échouent plus tard,
- ou des pannes après suspension/reprise de VM,
- ou une horloge système complètement fausse (
timedatectlmontre non synchronisé).
MTU et fragmentation : le cousin plus lent du handshake
Techniquement, les problèmes de MTU cassent plus souvent les données que le handshake, parce que les paquets de handshake sont petits. Mais en pratique, les gens diagnostiquent « handshake did not complete » parce que le tunnel « ne marche pas », et ils ne séparent jamais le plan de contrôle du plan de données.
Les problèmes MTU se manifestent par :
- le handshake fonctionne, mais les gros transferts stagnent,
- certains sites fonctionnent, d’autres pas (trou PMTUD classique),
- SSH marche, HTTPS bloque sur de grosses réponses,
- VoIP grésille et tout le monde blâme le codec.
Réglez-le comme un adulte
Commencez par le MTU par défaut de wg-quick (souvent 1420). Si vous avez de l’encapsulation additionnelle (PPPoE, VLANs, autres tunnels), baissez-le. Validez avec DF ping, puis passez au clamp MSS si vous routez beaucoup de TCP.
Trois micro-histoires d’entreprise du terrain
Incident 1 : la panne causée par une mauvaise hypothèse
Une entreprise moyenne a déployé WireGuard comme « VPN d’accès simple » pour les ingénieurs. Le document de conception disait : « Ouvrir UDP/51820 sur le serveur VPN. » Simple.
Ils ont déployé le serveur en colo derrière un pare-feu edge géré par une autre équipe réseau. L’équipe applicative a demandé « ouvrir le port 51820 ». L’équipe réseau a ouvert TCP/51820 parce que leur ticket système avait TCP par défaut, et personne n’a vérifié. Tout le monde a testé depuis le réseau interne, où un chemin de NAT hairpin interne faisait que les choses semblaient fonctionner « parfois », ce qui est le pire type de fonctionnement.
Le jour du lancement, les utilisateurs distants ont signalé « Handshake did not complete. » L’équipe applicative a passé des heures à tourner les clés et reconstruire des configs. Pendant ce temps, l’équipe réseau insistait « le port est ouvert ». Il l’était. Juste pas pour le bon protocole.
La correction a été un changement de pare‑feu d’une ligne autorisant UDP/51820 entrant. La leçon portait moins sur WireGuard que sur les hypothèses : n’acceptez jamais « port ouvert » sans « protocole ouvert », et ne dépannez jamais la cryptographie avant d’avoir confirmé que les paquets arrivent.
Incident 2 : une optimisation qui s’est retournée contre eux
Une autre organisation voulait « réduire le bavardage de fond » sur les clients mobiles pour économiser batterie et données. Quelqu’un a remarqué PersistentKeepalive = 25 et a décidé que c’était gaspillé. Ils l’ont supprimé pour toute la flotte.
Ça a fonctionné sur le Wi‑Fi du bureau et sur les forfaits LTE corporate. Puis le personnel de terrain a commencé à utiliser des réseaux aléatoires : Wi‑Fi d’hôtel, cafés, portails captifs d’aéroport. Ces réseaux avaient souvent des timeouts UDP agressifs. Les tunnels inactifs mouraient en silence. Quand les utilisateurs reprenaient une activité, certains trafics déclenchaient un nouveau handshake, mais pas toujours dans le bon ordre pour leurs applications. Ils ont vu des échecs intermittents : parfois ça marchait au bout de 10–30 secondes, parfois il fallait redémarrer l’application.
La réponse incident a porté sur les versions de WireGuard, les noyaux, et « peut-être la crypto est cassée ». Ce n’était pas le cas. L’« optimisation » avait retiré le mécanisme qui gardait les mappings NAT vivants.
Ils ont réintroduit le keepalive — de façon sélective. Les laptops toujours allumés ont eu le keepalive. Les téléphones ont eu un intervalle plus long et seulement pour les profils utilisés sur réseaux hostiles. La leçon plus profonde : optimiser sans modèle de panne, c’est se porter volontaire pour des futures pannes.
Incident 3 : la pratique ennuyeuse mais correcte qui a sauvé la journée
Une équipe de services financiers avait l’habitude, apparemment old‑fashioned : chaque changement de service réseau était accompagné d’une capture de paquets sur les deux extrémités pendant une fenêtre de test, stockée avec l’enregistrement du changement. Pas pour la conformité, mais parce qu’ils aimaient dormir.
Un vendredi, un changement de routage ISP mineur a déplacé l’IP publique du serveur WireGuard (planifié). Le DNS s’est mis à jour rapidement, mais un sous‑ensemble de clients frappait encore l’ancienne IP à cause du cache et d’un résolveur obsolète. Les utilisateurs voyaient « Handshake did not complete. » L’équipe d’astreinte a sorti la dernière capture de paquets et l’a comparée à une nouvelle en quelques minutes.
Les captures racontaient l’histoire : les clients envoyaient des UDP vers l’ancienne IP ; le serveur ne le voyait jamais. Pas besoin de toucher aux clés, au MTU, ou aux règles du pare‑feu. Ils ont réduit le TTL DNS pour cet enregistrement pendant la migration et ont poussé une IP endpoint mise à jour aux clients qui ne pouvaient pas se fier au DNS.
C’était ennuyeux, mesurable et rapide. La pratique n’était pas glamour, mais elle a transformé la devinette en un incident court.
Erreurs courantes : symptôme → cause racine → correctif
-
Symptôme : Le client affiche « latest handshake: (none) » ; tcpdump serveur ne montre aucun UDP entrant.
Cause racine : IP publique/endpoint erroné, pare‑feu en amont, redirection de port manquante, CGNAT, ou ISP qui bloque UDP.
Fix : vérifiez l’endpoint IP, ouvrez UDP à tous les niveaux de pare‑feu, configurez la redirection correcte, ou déplacez le serveur vers une IP publique vraie (ou IPv6). -
Symptôme : Le serveur voit des UDP entrants mais n’envoie jamais de réponses.
Cause racine : pare‑feu hôte qui droppe la sortie, routage politique envoie les réponses par la mauvaise interface, rp_filter bloque, ou WireGuard pas lié/démarré correctement.
Fix : autorisez UDP sortant, corrigez le routage, relaxez rp_filter si approprié, validezss -lunpetwg show. -
Symptôme : Le serveur répond dans tcpdump ; le client ne voit pas les réponses.
Cause racine : pare‑feu côté client, mapping NAT expiré, comportement NAT symétrique, ou chemin de retour bloqué.
Fix : ajoutezPersistentKeepalivesur le client, autorisez UDP entrant depuis le serveur, testez depuis un autre réseau, ou utilisez un serveur avec connectivité stable. -
Symptôme : Le handshake se termine, mais vous n’atteignez rien via le tunnel.
Cause racine : AllowedIPs incorrects, routes manquantes, IP forwarding désactivé, règles NAT/forward manquantes sur le serveur, ou sous‑réseaux qui se chevauchent.
Fix : corrigez AllowedIPs, vérifiezip route get, activez le forwarding, ajoutez des règles forward/NAT, renumérotez ou NATtez un côté. -
Symptôme : Ça marche une minute, puis ça meurt à l’inactivité ; le timestamp du handshake cesse de se mettre à jour.
Cause racine : timeout NAT UDP ; le mapping expire.
Fix :PersistentKeepalive = 25(ou valeur adaptée) sur le peer derrière NAT ; envisagez d’augmenter le timeout d’inactivité sur le pare‑feu si vous le contrôlez. -
Symptôme : Ça marche sur certains réseaux, jamais sur d’autres (surtout réseaux invités d’entreprise).
Cause racine : UDP bloqué ou limité en débit ; seul TCP/443 est autorisé en sortie.
Fix : fournissez un chemin d’egress alternatif (réseau différent), ou déployez une architecture qui n’exige pas d’UDP brut sortant pour ces clients (décision organisationnelle, pas un réglage WireGuard). -
Symptôme : Après reprise/restauration VM, les handshakes échouent de manière imprévisible.
Cause racine : saut d’horloge/dérive temporelle, état obsolète, ou cas limites de la protection contre la répétition.
Fix : assurez la synchro temporelle, redémarrez/descendez l’interface, évitez de restaurer de vieux snapshots comme « étape de réparation » pour des services réseau. -
Symptôme : Le handshake marche, les petits pings marchent, les gros téléchargements bloquent.
Cause racine : MTU / trou PMTUD ; fragmentation bloquée.
Fix : baissez le MTU WG, testez avec DF ping, clamp MSS TCP sur les chemins de routage.
Listes de contrôle / plan étape par étape
Checklist A: « Handshake never completes » (échec dur)
- Confirmer endpoint : IP et port de
Endpointdu client sont corrects. - Confirmer que le serveur écoute :
wg showetss -lunp. - Capture de paquets sur le serveur :
tcpdump -ni <if> udp port <port>. - Si le serveur ne voit rien : inspectez les redirections de port, les pare‑feu externes, et si le serveur a une vraie IP publique (pas 100.64/10).
- Si le serveur voit seulement l’entrée : vérifiez l’egress pare‑feu du serveur, le routage, et rp_filter.
- Si le serveur voit les deux directions : validez les clés et la synchronisation du temps ; ensuite vérifiez si un middlebox réécrit/blackhole les réponses côté client.
Checklist B: « Handshake completes but traffic doesn’t » (échec mou)
- Confirmer que les compteurs bougent :
wg showdoit indiquer des octets en hausse. - Vérifier AllowedIPs : assurez-vous que les réseaux de destination sont inclus du côté émetteur ; assurez-vous que les IPs tunnel du peer sont correctes du côté récepteur.
- Vérifier le routage :
ip route get <dest>doit pointer vers wg0 pour les destinations tunnelées. - Vérifier forwarding/NAT : si vous attendez un accès LAN, activez le forwarding et autorisez le trafic de la chaîne forward.
- Tester MTU : DF ping avec tailles croissantes ; ajustez le MTU WG ou clamp MSS.
- Puis DNS : seulement après que la connectivité IP fonctionne. Les problèmes DNS se font passer pour « VPN cassé » et font perdre des heures.
Checklist C: « It drops every few minutes » (panne intermittente)
- Observer les changements d’endpoint : le champ endpoint du serveur
wg showqui flappe indique un rebinding NAT. - Ajouter keepalive :
PersistentKeepalive = 25sur le côté derrière NAT ; ajustez selon besoin. - Vérifier les timeouts UDP : sur les pare‑feu que vous contrôlez, augmentez le timeout de session UDP pour ce port/hôte.
- Chercher des NAT concurrents : double NAT et isolation client des Wi‑Fi invités peuvent se comporter comme de la perte de paquets.
FAQ
1) Est-ce que « Handshake did not complete » signifie toujours que le port UDP est bloqué ?
Non. Ça signifie que les paquets de handshake ne sont pas échangés avec succès. UDP bloqué est fréquent, mais une IP endpoint erronée, un routage asymétrique, ou une incompatibilité de clés peuvent aussi le provoquer. Utilisez tcpdump pour séparer « aucun paquet n’arrive » de « les paquets arrivent mais le handshake échoue ».
2) Si je peux pinguer l’IP publique du serveur, pourquoi WireGuard ne handshake pas ?
Ping utilise ICMP. WireGuard utilise UDP. Les réseaux autorisent fréquemment ICMP mais bloquent ou limitent UDP, surtout sur des réseaux invités d’entreprise et certains ISP. Testez la reachabilité UDP avec des captures de paquets, pas avec des sensations.
3) Dois‑je changer le port WireGuard par défaut 51820 ?
Parfois. Si vous suspectez un filtrage en amont sur des ports VPN courants, passer à un port UDP élevé aléatoire peut aider. Mais ne le faites pas en mode cargo‑cult : si tcpdump ne montre rien sur le serveur, vous devez quand même ouvrir/forwarder le nouveau port partout.
4) Où dois‑je définir PersistentKeepalive ?
Sur le peer derrière NAT qui doit rester joignable. Généralement c’est le client. Le mettre sur le serveur public ne fait rien pour les mappings NAT côté client.
5) Des AllowedIPs incorrects peuvent‑ils empêcher le handshake ?
Oui, de plusieurs façons. Si le serveur attend que le peer utilise une certaine IP tunnel mais que AllowedIPs ne l’inclut pas, les paquets peuvent être rejetés comme « pas de ce peer ». Plus souvent, le handshake réussit mais les données ne circulent pas parce que le routage n’envoie rien dans le tunnel.
6) Pourquoi ça marche sur le hotspot de mon téléphone mais pas au bureau ?
Les réseaux de bureau bloquent souvent l’UDP sortant sauf pour DNS/NTP, ou ils appliquent une inspection stateful agressive qui tue les flows inactifs. Votre hotspot téléphone est généralement plus simple et plus tolérant envers l’UDP.
7) Dois‑je redémarrer WireGuard après avoir changé des règles de pare‑feu ?
Non, les changements de pare‑feu s’appliquent immédiatement. Le redémarrage peut aider à supprimer les confusions, mais ce n’est pas obligatoire. Préférez changer une variable à la fois pour savoir ce qui a résolu le problème.
8) Comment savoir si je suis derrière un CGNAT ?
Si l’interface WAN de votre machine a une IP dans 100.64.0.0/10, vous êtes derrière un CGNAT. Aussi, si l’IP WAN de votre routeur est privée (192.168/10.0/172.16), vous êtes derrière un autre NAT en amont. Le port forwarding entrant ne fonctionnera pas sauf si vous contrôlez aussi le NAT en amont.
9) Le handshake se termine mais seuls certains sous‑réseaux sont joignables. Pourquoi ?
Cela vient généralement d’un mismatch de routage/AllowedIPs ou de sous‑réseaux qui se chevauchent. Confirmez ip route get pour chaque destination et vérifiez que les AllowedIPs de chaque peer incluent les bons CIDR.
10) La synchronisation temporelle est‑elle vraiment pertinente pour WireGuard ?
La plupart du temps, non. Mais quand les horloges sont très mal réglées — surtout après suspension/reprise ou restauration de snapshot — la protection contre la répétition et l’état peuvent se comporter de façon à donner l’impression d’échecs de handshake aléatoires. Gardez NTP sain et ennuyeux.
Conclusion : prochaines étapes qui réduisent vraiment les incidents
Quand WireGuard dit « Handshake did not complete », il ne vous demande pas de méditer sur la cryptographie. Il vous demande de suivre les paquets. Faites la séquence ennuyeuse :
- Vérifiez endpoint/port et que le serveur écoute.
- Capturez des paquets sur le serveur : des UDP entrants arrivent‑ils ?
- Si oui, les réponses sortent‑elles et le client les reçoit‑il ?
- Si les handshakes réussissent, arrêtez de regarder les handshakes et corrigez routage/AllowedIPs/forwarding/MTU.
Opérationnellement, votre meilleur gain à long terme est de standardiser un lot minimal de diagnostics : sortie wg show, ss -lunp, un tcpdump de 30 secondes sur les deux côtés, et timedatectl. Gardez ça en réflexe. Votre futur vous remerciera — silencieusement, parce qu’il dort enfin.