Installer un VPN WireGuard sur son propre serveur
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-toolsrecommande 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 lignesiptables -t nat -A POSTROUTINGrecopiées partout n'y servent donc à rien tant que vous n'installez pas le paquet. - Transmission du DNS : pour la ligne
DNS =,wg-quickappelle systématiquement le programmeresolvconf. Sur Ubuntu, le paquetsystemd-resolvedfournit une couche de compatibilité ; sur une installation Debian minimale,resolvconfn'existe souvent pas du tout. Pour l'installer, le paquet adéquat s'appelleopenresolvsur 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_forwardest à 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 :
- 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.keydoit donner exactement le contenu deserver.pub. - Saut de ligne copié avec le reste. Les clés copiées à la souris depuis un terminal traînent volontiers des espaces.
wgle sanctionne parKey is not the correct length or format. - Droits sur les fichiers. Si vous oubliez
umask 077,wg-quickvous avertit au démarrage avecWarning: `/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 avectcpdump -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
tcpdumpmontre des paquets. Dans ce cas, WireGuard écoute sur un autre port ou sur une autre adresse. Contrôle avecss -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ème | Paquet adéquat |
|---|---|
| Debian 11, Debian 12, Debian 13 | apt-get install -y openresolv |
| Ubuntu 22.04 | apt-get install -y openresolv |
| Ubuntu 24.04 | apt-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 ?
Pourquoi les lignes iptables de nombreux tutoriels ne fonctionnent-elles pas sur Debian ?
Le handshake passe, mais je n'arrive pas sur Internet. D'où cela vient-il ?
Comment reconnaître un problème de MTU ?
Dois-je exploiter mon propre serveur DNS pour que le DNS fonctionne dans le tunnel ?
Pourquoi un appareil ne passe-t-il plus après avoir remis son horloge à l'heure ?
2026 KernelHost GmbH. Tous droits réservés. Ce guide est protégé par le droit d'auteur. Sa republication sur d'autres sites web, même partielle ou sous une forme modifiée, n'est pas autorisée sans notre accord écrit. Les citations accompagnées de la source et d'un lien sont expressément les bienvenues.

