Installer un VPN WireGuard sur son propre serveur

Publié le 16 min de lecture

Du serveur vide au tunnel WireGuard qui tourne : paires de clés, NAT avec nftables, wg-quick en service, QR code pour le téléphone et les trois pannes qui bloquent vraiment.

Ce qui est identique sur les quatre distributions, et ce qui ne l'est pas

WireGuard est solidement intégré au noyau Linux depuis la version 5.6. Sur Debian 13, Debian 12, Ubuntu 24.04 et Ubuntu 22.04, vous n'avez donc plus à compiler un module DKMS, à activer un dépôt de backports ni à importer une clé tierce. Les quatre livrent en plus le même état des outils userland (version upstream 1.0.20210914), ce qui veut dire que les commandes wg et wg-quick se comportent partout de façon identique.

Les différences se situent uniquement autour, et c'est précisément là que la plupart des tutoriels échouent :

  • Filtre de paquets : wireguard-tools recommande nftables ou iptables. Comme apt retient la première alternative disponible, c'est nftables qui atterrit sur une installation Debian légère, pas iptables. Les lignes iptables -t nat -A POSTROUTING recopiées partout n'y servent donc à rien tant que vous n'installez pas le paquet.
  • Transmission du DNS : pour la ligne DNS =, wg-quick appelle systématiquement le programme resolvconf. Sur Ubuntu, le paquet systemd-resolved fournit une couche de compatibilité ; sur une installation Debian minimale, resolvconf n'existe souvent pas du tout. Pour l'installer, le paquet adéquat s'appelle openresolv sur Debian et sur Ubuntu 22.04 ; sur Ubuntu 24.04, ce paquet n'existe plus. Cela ne concerne que les clients Linux, pas les téléphones.
  • Frontend de pare-feu : les images Ubuntu embarquent souvent un ufw actif, Debian en général pas. ufw bloque le transfert par défaut, même si net.ipv4.ip_forward est à 1.

Vérifiez d'abord ce que vous avez sous la main :

apt-get update
apt-get install -y wireguard wireguard-tools nftables qrencode
wg --version
apt-cache policy wireguard-tools

Si wg --version affiche un numéro de version, la partie userland est en place. Pour savoir si le noyau suit, il faut attendre le premier wg-quick up. Si le message RTNETLINK answers: Operation not supported ou Unable to access interface: Protocol not supported reste affiché, vous tournez sur un noyau sans prise en charge de WireGuard, typiquement un noyau très ancien ou un noyau de conteneur (container) fortement allégé.

Générer les paires de clés sans se créer d'ennuis

WireGuard ne connaît ni noms d'utilisateur ni certificats. Il y a exactement une paire de clés par participant, plus éventuellement une clé pré-partagée commune (preshared key) comme couche symétrique supplémentaire. Générez les deux avec une umask définie, sinon les clés privées traînent en lecture pour tout le monde :

umask 077
wg genkey | tee /etc/wireguard/server.key | wg pubkey > /etc/wireguard/server.pub
wg genkey | tee /etc/wireguard/telephone.key | wg pubkey > /etc/wireguard/telephone.pub
wg genpsk > /etc/wireguard/telephone.psk
ls -l /etc/wireguard

Vous n'avez pas besoin de créer le répertoire /etc/wireguard vous-même, le paquet wireguard-tools le livre déjà en mode 0700. Plus important, une particularité de umask : la valeur ne vaut que dans la session shell où vous la définissez. Si vous générez les clés en deux étapes, par exemple parce que la connexion a été coupée entre-temps et que vous vous êtes reconnecté, vous retravaillez ensuite avec le masque par défaut, et server.key ainsi que telephone.psk se retrouvent sur le disque en 0644. Fixez donc les droits une nouvelle fois de façon explicite pour terminer :

chmod 600 /etc/wireguard/*.key /etc/wireguard/*.psk

Trois erreurs reviennent sans arrêt à cet endroit :

  1. Clé publique et clé privée inversées. Les deux sont des chaînes Base64 de 44 caractères et se ressemblent trait pour trait. Si une clé publique se glisse par mégarde dans [Interface] PrivateKey, le tunnel démarre quand même, mais aucun handshake n'aboutit jamais. La vérification est possible à tout moment : wg pubkey < /etc/wireguard/server.key doit donner exactement le contenu de server.pub.
  2. Saut de ligne copié avec le reste. Les clés copiées à la souris depuis un terminal traînent volontiers des espaces. wg le sanctionne par Key is not the correct length or format.
  3. Droits sur les fichiers. Si vous oubliez umask 077, wg-quick vous avertit au démarrage avec Warning: `/etc/wireguard/wg0.conf' is world accessible. Ce n'est pas de la cosmétique, ce fichier contient la clé privée en clair.

La configuration du serveur

Déterminez d'abord le nom de votre interface Internet. Sur les machines virtuelles, elle s'appelle eth0, ens3 ou enp1s0 selon l'image :

ip route show default

Créez ensuite /etc/wireguard/wg0.conf. Le réseau du tunnel devrait être un réseau que vous ne croiserez pas dans le Wi-Fi d'un hôtel en déplacement, donc plutôt pas 192.168.0.0/24 ni 192.168.1.0/24 :

[Interface]
Address = 10.8.0.1/24
ListenPort = 51820
PrivateKey = CONTENU_DE_SERVER_KEY

PostUp = nft add table ip wgnat
PostUp = nft add chain ip wgnat postrouting '{ type nat hook postrouting priority srcnat; policy accept; }'
PostUp = nft add rule ip wgnat postrouting ip saddr 10.8.0.0/24 oifname "eth0" masquerade
PostDown = nft delete table ip wgnat

[Peer]
# telephone
PublicKey = CONTENU_DE_TELEPHONE_PUB
PresharedKey = CONTENU_DE_TELEPHONE_PSK
AllowedIPs = 10.8.0.2/32

Remplacez eth0 par votre interface réelle. Si vous préférez rester sur iptables, installez iptables et prenez ceci à la place :

PostUp = iptables -t nat -A POSTROUTING -s 10.8.0.0/24 -o eth0 -j MASQUERADE
PostDown = iptables -t nat -D POSTROUTING -s 10.8.0.0/24 -o eth0 -j MASQUERADE

Deux détails rarement mentionnés. Premièrement : chaque ligne PostUp est exécutée par un shell, et si l'une d'elles s'interrompt sur une erreur, wg-quick annule tout le démarrage. Une faute de frappe dans la règle nft ne donne donc pas un tunnel à moitié fonctionnel, mais pas de tunnel du tout. Deuxièmement : AllowedIPs a une signification totalement différente côté serveur et côté client. Ici, c'est une table d'affectation qui dit quelle adresse source appartient à quel peer. Si vous saisissez la même adresse pour deux peers, c'est le dernier chargé qui l'emporte, et l'autre reste muet. Chaque client reçoit exactement un /32.

Fixez les droits et vérifiez la syntaxe avant de démarrer :

chmod 600 /etc/wireguard/wg0.conf
wg-quick strip wg0

wg-quick strip affiche la configuration sans les lignes propres à wg-quick. Si la commande passe, le fichier est formellement en ordre. Si elle signale wg-quick: Line unrecognized, vous avez le plus souvent écrit une option dans la mauvaise section, par exemple DNS sous [Peer].

Activer durablement le transfert IP

Sans transfert, chaque paquet s'arrête dans le serveur. Vous l'activez immédiatement et durablement :

echo 'net.ipv4.ip_forward=1' > /etc/sysctl.d/99-wireguard.conf
sysctl --system
sysctl net.ipv4.ip_forward

La dernière commande doit afficher net.ipv4.ip_forward = 1. Si vous voulez faire passer IPv6 par le tunnel, ajoutez net.ipv6.conf.all.forwarding=1 dans le même fichier.

Si ufw tourne, cela ne suffit pas encore. ufw applique sa propre politique de transfert, qui agit indépendamment du commutateur du noyau. Ouvrez le port et autorisez le passage de façon explicite :

ufw allow 51820/udp
ufw route allow in on wg0 out on eth0

Le tableau typique d'un transfert oublié est particulièrement sournois : le handshake passe, le ping vers 10.8.0.1 passe, mais rien de ce qui se trouve derrière n'est joignable. Celui qui ne regarde que le handshake cherche alors pendant des heures au mauvais endroit.

Mettre en place wg-quick comme service

Le paquet fournit une unit modèle, le nom après le @ est le nom du fichier de configuration sans extension :

systemctl enable wg-quick@wg0
systemctl start wg-quick@wg0
systemctl status wg-quick@wg0
journalctl -u wg-quick@wg0 -n 50 --no-pager

Un piège que beaucoup ne remarquent qu'au bout de plusieurs semaines : SaveConfig = true. Cette option réécrit l'état courant dans le fichier au moment où le service s'arrête. Tous les commentaires, toutes les lignes PostUp dans leur ordre d'origine et toute structure insérée à la main sont alors perdus. Pour un serveur dont vous entretenez la configuration à la main, laissez cette option de côté.

Après le démarrage, vérifiez l'état non pas sur le code de retour, mais sur l'interface :

wg show
ip -brief address show wg0
ss -ulpn

ss -ulpn doit montrer un processus à l'écoute sur le port UDP 51820. wg show liste les peers, à ce stade encore sans handshake.

Configuration du client et QR code pour le téléphone

Le mieux est de créer le fichier client sur le serveur, puisque toutes les clés s'y trouvent de toute façon. Attention : côté client, AllowedIPs signifie autre chose, à savoir quelles destinations doivent passer par le tunnel. 0.0.0.0/0, ::/0 veut dire : tout.

mkdir -p /etc/wireguard/clients

Contenu de /etc/wireguard/clients/telephone.conf :

[Interface]
PrivateKey = CONTENU_DE_TELEPHONE_KEY
Address = 10.8.0.2/32
DNS = 9.9.9.9, 149.112.112.112

[Peer]
PublicKey = CONTENU_DE_SERVER_PUB
PresharedKey = CONTENU_DE_TELEPHONE_PSK
AllowedIPs = 0.0.0.0/0, ::/0
Endpoint = votre.serveur.adresse:51820
PersistentKeepalive = 25

PersistentKeepalive = 25 n'est pas un luxe sur les téléphones. Le NAT des réseaux mobiles oublie souvent les associations UDP au bout de 30 à 60 secondes, et sans keepalive, le serveur ne peut plus solliciter de lui-même une connexion existante.

Vous générez le QR code directement dans le terminal :

qrencode -t ansiutf8 < /etc/wireguard/clients/telephone.conf

Dans l'application WireGuard, appuyez sur le plus, choisissez l'import par QR code et pointez la caméra sur le terminal. Deux conseils pratiques : réduisez la taille de police du terminal avant de générer le code, sinon il ne tient pas dans l'image. Et ne supprimez pas le fichier tout de suite après, vous en aurez de nouveau besoin lors d'un changement d'appareil. Si vous le supprimez quand même, il faudra générer une nouvelle paire de clés, car la clé privée ne se recalcule pas à partir de la clé publique.

À quoi vous voyez que cela fonctionne vraiment

Le fait que systemctl start se termine sans sortie signifie seulement que l'interface existe. Le vrai test comporte trois étapes, et vous devriez les parcourir dans cet ordre :

wg show wg0 latest-handshakes
wg show wg0 transfer

Étape un, le handshake. latest-handshakes affiche un horodatage Unix par peer. S'il indique 0, aucune connexion n'a jamais abouti. Dans l'affichage détaillé de wg show, vous lisez à la place latest handshake: 42 seconds ago.

Étape deux, le flux de données. Sous transfer figurent les octets reçus et envoyés. Des octets envoyés sans octets reçus signifient : vos paquets partent, rien ne revient. C'est presque toujours un pare-feu ou un endpoint erroné, jamais un problème de clé.

Étape trois, le chemin réellement emprunté. Sur le client, vérifiez si le trafic est bien dirigé dans le tunnel :

ip route get 1.1.1.1

Si vous y lisez dev wg0, le routage est correct. C'est seulement après qu'un coup d'œil sur une page affichant votre adresse IP publique a du sens. Si elle montre l'adresse de votre serveur, vous avez terminé.

Dépannage : pas de handshake

Le tableau d'erreur le plus fréquent de tous. Activez d'abord la journalisation du module noyau, elle donne la réponse en dix secondes la plupart du temps :

echo 'module wireguard +p' > /sys/kernel/debug/dynamic_debug/control
dmesg -w

Lancez maintenant une tentative de connexion depuis le client et lisez au fil de l'eau. Les trois messages qui comptent :

  • wireguard: wg0: Handshake for peer 1 (...) did not complete after 5 seconds, retrying (try 2) et rien d'autre. Aucun paquet n'arrive sur le serveur. Contrôlez avec tcpdump -n -i eth0 udp port 51820. Si vous n'y voyez rien, la cause est le pare-feu placé devant le serveur, le port, ou le fait que le client se trouve dans un réseau qui filtre l'UDP sortant. Un test depuis le réseau mobile plutôt que depuis le Wi-Fi de l'entreprise sépare proprement ces cas.
  • wireguard: wg0: Invalid handshake initiation from .... Des paquets arrivent, mais ils ne correspondent pas. Presque toujours, la clé publique du serveur inscrite dans le fichier client est fausse, ou la clé pré-partagée n'est renseignée que d'un seul côté. Une clé pré-partagée doit être identique des deux côtés, ou absente des deux côtés.
  • Aucun message du tout, alors que tcpdump montre des paquets. Dans ce cas, WireGuard écoute sur un autre port ou sur une autre adresse. Contrôle avec ss -ulpn.

Un cas particulier rarement documenté est l'heure. WireGuard glisse un horodatage dans le premier message de handshake et le serveur retient, pour chaque peer, la plus grande valeur jamais vue. Il rejette les horodatages plus anciens, c'est la protection contre le rejeu. Si un appareil avait son horloge réglée loin dans le futur et s'est connecté une fois, il ne passe plus une fois l'horloge corrigée. Le serveur garde cet état en mémoire, la solution est donc : remettre l'horloge à l'heure, puis lancer wg-quick down wg0 et wg-quick up wg0 sur le serveur. Après la reconstruction, le blocage a disparu.

Désactivez ensuite la journalisation, elle est bavarde :

echo 'module wireguard -p' > /sys/kernel/debug/dynamic_debug/control

Dépannage : le DNS ne fonctionne pas

Symptôme : le tunnel est établi, ping 1.1.1.1 passe, mais aucun nom ne se résout. Il y a exactement trois causes.

Premièrement : le résolveur n'est pas joignable. Si vous saisissez DNS = 10.8.0.1, un serveur de noms doit réellement écouter sur cette adresse du côté du serveur. Un Debian ou un Ubuntu nu n'en a aucun. Soit vous indiquez un résolveur public qui est joint par le tunnel, soit vous en installez un vous-même. Pour la seconde voie, dnsmasq suffit, avec un petit fichier sous /etc/dnsmasq.d/wireguard.conf :

interface=wg0
bind-dynamic
no-resolv
server=9.9.9.9
server=1.1.1.1
cache-size=1000

bind-dynamic est important, car wg0 n'existe éventuellement pas encore au démarrage de dnsmasq. no-resolv est obligatoire sur Ubuntu : sans cette ligne, dnsmasq lit /etc/resolv.conf, y trouve l'adresse stub 127.0.0.53 de systemd-resolved et crée une boucle. Et le point qui mord le plus souvent sur Ubuntu : dnsmasq occupe le port 53. Sur un système où systemd-resolved est actif, ce port est déjà pris, les deux services entrent en collision et dnsmasq ne démarre pas. Les lignes interface=wg0 et bind-dynamic ne sont donc pas cosmétiques, elles empêchent dnsmasq de se lier à toutes les adresses. Vérifiez ensuite avec ss -ulpn que dnsmasq et systemd-resolved ne se disputent pas le port 53.

Deuxièmement : le résolveur se trouve hors des AllowedIPs. Si, au lieu de 0.0.0.0/0, vous n'envoyez que certains réseaux dans le tunnel et que vous indiquez comme serveur DNS une adresse absente de cette liste, les requêtes passent à côté du tunnel et partent dans le réseau local.

Troisièmement, uniquement sur les clients Linux : resolvconf manque. La ligne DNS = est transmise par wg-quick à un programme nommé resolvconf. S'il manque, le démarrage s'interrompt avec /usr/bin/wg-quick: line 32: resolvconf: command not found. Vérifiez d'abord si le programme est présent :

command -v resolvconf

Pour l'installation, les distributions divergent, et c'est exactement là que les tutoriels recopiés échouent sur Ubuntu 24.04. Le paquet openresolv n'y existe ni dans main ni dans universe, et l'appel se termine par E: Package 'openresolv' has no installation candidate :

SystèmePaquet adéquat
Debian 11, Debian 12, Debian 13apt-get install -y openresolv
Ubuntu 22.04apt-get install -y openresolv
Ubuntu 24.04apt-get install -y resolvconf

Sur Ubuntu 24.04, resolvconf est un paquet virtuel qui se résout sans ambiguïté vers systemd-resolved et crée au passage /usr/sbin/resolvconf, c'est-à-dire exactement le binaire que wg-quick appelle pour la ligne DNS. Vous pouvez tout aussi bien y installer directement systemd-resolved. Si vous voulez une ligne qui fonctionne sur tous les systèmes cités, prenez celle-ci :

apt-get install -y openresolv || apt-get install -y resolvconf

Sur Ubuntu 24.04, il existe une variante de ce problème qui apparaît après une mise à niveau depuis 22.04 : Failed to resolve interface "tun.wg0": No such device. La cause est un ancien paquet resolvconf resté en place depuis 22.04, avec son /etc/resolvconf/interface-order, donc pas le paquet virtuel du même nom de 24.04. En se basant sur ce fichier, wg-quick ajoute un tun. devant le nom de l'interface, ce dont la couche de compatibilité de systemd-resolved ne sait rien faire. La solution consiste à supprimer l'ancien paquet, de sorte qu'il ne reste plus que la couche de compatibilité de systemd-resolved.

Dépannage : problèmes de MTU

Le tableau d'erreur le plus désagréable, parce que tout semble fonctionner. Le handshake tient, le ping passe, SSH passe, mais les pages web ne se construisent qu'à moitié et se figent, les gros téléchargements s'interrompent, et c'est justement HTTPS qui est touché. La raison : les petits paquets passent, les gros non.

WireGuard ajoute 60 octets autour de chaque paquet quand le tunnel passe par IPv4 (20 octets IP, 8 octets UDP, 32 octets WireGuard), et 80 octets par IPv6. wg-quick retire donc forfaitairement 80 octets de la MTU de chemin déterminée et arrive à 1420 sur un trajet normal en 1500. C'est volontairement conservateur et cela convient dans la plupart des cas.

Cela ne convient plus quand le chemin est plus étroit que 1500, par exemple avec du DSL en PPPoE (1492), derrière un tunnel supplémentaire ou sur certains réseaux mobiles. Mesurez la MTU de chemin réelle depuis le client vers l'adresse publique du serveur, avec le bit Don't Fragment positionné et sans tunnel :

ping -M do -s 1472 -c 3 ADRESSE_CIBLE

1472 plus 28 octets d'en-tête donnent 1500. Si ping: local error: message too long, mtu=... ou Frag needed and DF set revient, baissez la valeur par paliers jusqu'à ce que cela passe : 1464, 1444, 1414, 1372. À la valeur trouvée, ajoutez 28 et retirez 80. Pour 1464, cela ferait donc 1492 de MTU de chemin et 1412 de MTU de tunnel.

Cela se renseigne dans la section [Interface], du côté qui a le problème :

MTU = 1412

Le contre-test rapide, avant de vous lancer dans de longs calculs : mettez MTU = 1280 à titre d'essai. C'est la plus petite MTU que garantit IPv6, et elle fonctionne pratiquement partout. Si vos pages se chargent alors proprement, c'était bien la MTU, et vous pouvez chercher tranquillement la valeur optimale. Si le problème persiste, il vient d'ailleurs.

Sur le serveur lui-même, une MTU erronée est plus rare, mais possible : si la valeur y est plus élevée que ce que le trajet supporte, vous n'en voyez l'effet que vers certaines destinations. Un coup d'œil à ip -brief address show wg0 et ip link show wg0 montre la valeur actuellement définie.

Modifier les peers en production sans éjecter tout le monde

Le réflexe qui consiste à taper systemctl restart wg-quick@wg0 après chaque modification coupe toutes les connexions existantes et reconstruit les règles NAT. Sur un serveur avec plusieurs utilisateurs, c'est inutilement brutal. WireGuard sait aligner la configuration à chaud :

wg syncconf wg0 <(wg-quick strip wg0)

La commande compare le fichier avec l'état courant et ne modifie que les différences. Les peers existants conservent leur session. Notez que la substitution de processus avec <(...) exige un bash ou un zsh, dans un sh pur elle échoue. Vous pouvez aussi ajouter un peer isolé directement :

wg set wg0 peer CLE_PUBLIQUE allowed-ips 10.8.0.3/32

Cette modification ne vit qu'en mémoire. Écrivez-la aussi dans le fichier de configuration, sinon le peer aura disparu au prochain redémarrage. C'est d'ailleurs la cause la plus fréquente de la phrase « hier, ça marchait encore ».

Si vous voulez exploiter un point de terminaison WireGuard durablement et avec une adresse stable, un serveur à vous est la base évidente. Chez KernelHost, les serveurs root KVM et les serveurs dédiés tournent dans le datacenter maincubes de Francfort-sur-le-Main (TÜV TIER3+) sur notre propre réseau, en PrePaid et sans durée minimale. Nos articles sur la sécurisation de SSH et sur la mise en place d'ufw complètent utilement ce sujet.

Questions fréquentes

Quelle version de WireGuard se trouve dans Debian 13, Debian 12, Ubuntu 24.04 et Ubuntu 22.04 ?
Les quatre livrent la même version upstream des outils, 1.0.20210914, chacune avec sa révision propre à la distribution. Le module WireGuard lui-même vient du noyau depuis la version 5.6 et n'a pas besoin d'être compilé séparément. Les commandes wg et wg-quick se comportent de façon identique sur les quatre systèmes, les différences portent sur le filtre de paquets (nftables ou iptables), sur resolvconf et sur ufw. Pour installer resolvconf, le paquet s'appelle openresolv sur Debian et sur Ubuntu 22.04 ; sur Ubuntu 24.04, openresolv n'existe plus et vous installez resolvconf.
Pourquoi les lignes iptables de nombreux tutoriels ne fonctionnent-elles pas sur Debian ?
Le paquet wireguard-tools recommande nftables ou iptables comme alternative. apt installe la première alternative disponible, donc nftables. Sur une installation Debian légère, iptables n'est alors pas présent du tout, la ligne PostUp échoue et cela annule tout le démarrage de wg-quick. Soit vous utilisez la variante nft, soit vous installez iptables de façon explicite.
Le handshake passe, mais je n'arrive pas sur Internet. D'où cela vient-il ?
Vérifiez dans cet ordre : net.ipv4.ip_forward doit valoir 1, la règle NAT doit nommer la bonne interface de sortie (ip route show default l'affiche), et avec un ufw actif il faut en plus ufw route allow in on wg0 out on eth0. Le commutateur du noyau seul ne suffit pas avec ufw, car ufw applique sa propre politique de transfert.
Comment reconnaître un problème de MTU ?
Le cas typique : le tunnel est établi, le ping et SSH fonctionnent, mais les pages web ne se construisent qu'à moitié et les gros téléchargements s'interrompent. Mettez MTU = 1280 à titre d'essai dans la section [Interface] du client. Si le problème disparaît, c'était bien la MTU. Vous déterminez la valeur optimale avec ping -M do vers l'adresse du serveur, en baissant la charge utile jusqu'à ce qu'elle passe, puis en ajoutant 28 et en retirant 80.
Dois-je exploiter mon propre serveur DNS pour que le DNS fonctionne dans le tunnel ?
Non. Vous pouvez simplement indiquer un résolveur public dans la configuration du client, il sera alors joint par le tunnel. Un résolveur à vous sur le serveur (par exemple dnsmasq sur wg0) devient intéressant si vous voulez résoudre des noms internes ou mettre les requêtes en cache. Le seul point important : si vous saisissez DNS = 10.8.0.1, un serveur de noms doit réellement écouter à cette adresse.
Pourquoi un appareil ne passe-t-il plus après avoir remis son horloge à l'heure ?
WireGuard se protège du rejeu avec un horodatage placé dans le premier message de handshake. Le serveur retient pour chaque peer la plus grande valeur vue et rejette les valeurs plus anciennes. Si un appareil s'est connecté une fois avec une horloge réglée dans le futur, il est refusé une fois l'heure corrigée. Cet état réside en mémoire, un wg-quick down wg0 suivi d'un wg-quick up wg0 sur le serveur règle donc le problème.

WireGuard VPN Debian Ubuntu nftables Réseau Tutoriel