Héberger votre VPN WireGuard sur un VPS Linux
Configurez WireGuard sur votre VPS Linux avec wg0.conf, les clés, le forwarding, le NAT et le DNS, puis corrigez les erreurs de handshake et d’AllowedIPs.
Ce que vous allez mettre en place
Un VPN WireGuard sur un serveur que vous administrez représente environ quarante lignes de configuration : une paire de clés, un fichier d’interface, un paramètre sysctl, une règle NAT et une ouverture dans le pare-feu. L’installation est triviale. L’essentiel de ce guide porte donc sur les problèmes courants, les permissions des clés, AllowedIPs, le forwarding et le DNS.
WireGuard est un tunnel de couche 3 intégré au noyau. Il est inclus dans le noyau Linux principal depuis la version 5.6. Ubuntu 24.04 et Debian 13 le fournissent donc sans module externe. Il n’y a ni négociation de chiffrement, ni autorité de certification, ni étape avec nom d’utilisateur et mot de passe : un peer est une clé publique associée aux adresses IP que cette clé peut utiliser. Un paquet dont la vérification MAC échoue est supprimé sans réponse. Le port ne répond donc pas aux scans. En contrepartie, il n’existe aucun serveur d’authentification : pour supprimer un accès, il faut supprimer le peer sur le serveur.
Vérifiez d’abord la virtualisation
WireGuard a besoin d’un kernel dans lequel il peut charger un module. Sur un VPS KVM, il fonctionne immédiatement. Avec une virtualisation par conteneurs qui partage le kernel de l’hôte, comme OpenVZ ou LXC, la première commande échoue avec RTNETLINK answers: Operation not supported. Dans ce cas, utilisez l’implémentation userspace wireguard-go. Vérifiez d’abord avec sudo modprobe wireguard && echo ok.
Générez les clés sans les exposer
Un /etc/wireguard/server.key lisible par tous revient à ne pas avoir de VPN du tout. La ligne umask 077 && wg genkey | sudo tee ... courante n’est pas fiable, car sudo applique son propre umask au fichier que tee crée. Définissez explicitement le mode.
sudo apt update && sudo apt install -y wireguard nftables
sudo install -d -m 700 /etc/wireguard
sudo sh -c 'umask 077; wg genkey > /etc/wireguard/server.key'
sudo sh -c 'wg pubkey < /etc/wireguard/server.key > /etc/wireguard/server.pub'
sudo chmod 600 /etc/wireguard/server.keyGénérez la paire côté client de la même manière. wg genpsk ajoute une clé prépartagée facultative, à raison d’une ligne dans chaque configuration.
L’interface du serveur : /etc/wireguard/wg0.conf
[Interface]
Address = 10.8.0.1/24
ListenPort = 51820
PrivateKey = <contents of /etc/wireguard/server.key>
[Peer]
PublicKey = <laptop public key>
PresharedKey = <psk, optional>
AllowedIPs = 10.8.0.2/32chmod 600 ce fichier ; un avertissement au démarrage indiquant qu’il est accessible à tous signifie que vous avez oublié cette étape. Address est l’adresse du serveur à l’intérieur du tunnel, avec le masque de l’ensemble du sous-réseau VPN. Choisissez une plage que vous ne rencontrerez pas sur Internet. 192.168.1.0/24 entre en conflit avec la moitié des routeurs domestiques derrière lesquels se trouvent vos clients, et le tunnel perd alors silencieusement face à la route locale.
Le AllowedIPs d’un pair du côté serveur est un /32, l’unique adresse du tunnel attribuée à ce client. Si vous attribuez la même adresse IP autorisée à deux pairs, elle est associée à celui qui a été configuré en dernier, et le premier ne reçoit plus de trafic sans qu’aucune erreur ne soit affichée. Laissez SaveConfig non défini, sinon wg-quick down réécrit ce fichier à partir de l’état actif.
Transformer le serveur en routeur
Un serveur Linux supprime les paquets qui ne lui sont pas destinés. Le forwarding et le NAT source sont tous les deux désactivés par défaut.
printf 'net.ipv4.ip_forward = 1\nnet.ipv6.conf.all.forwarding = 1\n' \
| sudo tee /etc/sysctl.d/99-wireguard.conf
sudo sysctl --system
sysctl net.ipv4.ip_forwardUn simple sysctl -w fonctionne jusqu’au prochain redémarrage, puis cesse discrètement de fonctionner. Le NAT a besoin de l’interface de sortie, c’est-à-dire la carte réseau qui donne accès à Internet, et non de wg0. Ne supposez pas eth0 ; récupérez sa valeur avec ip route show default, car les images actuelles utilisent des noms comme enp1s0 ou ens3.
Pare-feu : le port et le chemin de transfert
Un seul fichier nftables couvre le filtrage et le NAT. Écrivez /etc/nftables.conf : cette commande vide le ruleset existant. Ignorez donc cette étape sur un serveur déjà géré par ufw ou Docker.
#!/usr/sbin/nft -f
flush ruleset
table inet filter {
chain input {
type filter hook input priority filter; policy drop;
ct state established,related accept
iif lo accept
tcp dport 22 accept
udp dport 51820 accept
}
chain forward {
type filter hook forward priority filter; policy drop;
ct state established,related accept
iifname "wg0" oifname "enp1s0" accept
}
}
table ip nat {
chain postrouting {
type nat hook postrouting priority srcnat; policy accept;
ip saddr 10.8.0.0/24 oifname "enp1s0" masquerade
}
}Appliquez-le avec sudo systemctl enable --now nftables, en gardant une deuxième session SSH ouverte : policy drop et une erreur dans la règle SSH vous bloqueraient sur votre propre serveur. Notez ce que la chaîne forward n’autorise pas, wg0 à wg0. Les pairs peuvent accéder à Internet, mais pas les uns aux autres ; ajoutez iifname "wg0" oifname "wg0" accept pour un VPN pair-à-pair. Cette même chaîne contrôle ce qu’un pair peut atteindre directement sur le serveur, ce qui compte lorsque la machine sert aussi de machine de développement distante exécutant Claude Code dans tmux et que vous préférez ne pas exposer publiquement cette partie de sa configuration.
Sur un serveur équipé d’ufw : ufw allow 51820/udp, DEFAULT_FORWARD_POLICY="ACCEPT" dans /etc/default/ufw, et une règle POSTROUTING MASQUERADE *nat en tête de /etc/ufw/before.rules.
Lancer le service avec systemd
sudo systemctl enable --now wg-quick@wg0
sudo wg showwg-quick crée l’interface, ajoute les adresses et installe les routes dérivées de AllowedIPs. enable --now est la partie importante : un wg-quick up wg0 lancé manuellement disparaît au prochain redémarrage, et les mises à niveau du noyau imposent des redémarrages. Une unité qui ne redémarre pas après l’un de ces redémarrages reste silencieuse jusqu’à ce que quelqu’un tente de se connecter. Un drop-in OnFailure= sur wg-quick@wg0, configuré pour utiliser votre propre serveur ntfy, est donc le moyen le moins coûteux d’être alerté sur votre téléphone, plutôt que par un utilisateur bloqué à l’extérieur.
La configuration client et le paramètre que tout le monde interprète mal
[Interface]
PrivateKey = <laptop private key>
Address = 10.8.0.2/32
DNS = 10.8.0.1
[Peer]
PublicKey = <server public key>
Endpoint = vpn.example.com:51820
AllowedIPs = 0.0.0.0/0, ::/0
PersistentKeepalive = 25AllowedIPs remplit deux fonctions différentes, et les confondre est à l’origine de la plupart des problèmes de compréhension de WireGuard.
En sortie, c’est une table de routage. Un paquet dont la destination correspond à l’AllowedIPs d’un peer est chiffré et envoyé à ce peer. 0.0.0.0/0, ::/0 envoie tout le trafic dans le tunnel : c’est un full tunnel, avec le serveur comme route par défaut. Un split tunnel utilise une liste plus limitée : AllowedIPs = 10.8.0.0/24, 10.20.0.0/16 transporte le trafic VPN ainsi qu’un réseau privé situé derrière le serveur, tandis que tout le reste conserve sa route locale. Cette liste limitée permet de garder certains services complètement hors de l’Internet public : une instance Nextcloud privée sur un VPS liée à l’adresse du tunnel, ou les machines virtuelles du laboratoire de nested virtualisation exécutées sur le même serveur, restent accessibles aux peers et invisibles pour les autres.
En entrée, c’est une liste de contrôle d’accès. Un paquet déchiffré provenant d’un peer, dont l’adresse source ne figure pas dans l’AllowedIPs de ce peer, est supprimé. C’est pourquoi le serveur définit 10.8.0.2/32 pour l’ordinateur portable : une entrée 0.0.0.0/0 à cet endroit permettrait à ce client d’usurper n’importe quelle adresse du tunnel.
PersistentKeepalive sert aux clients situés derrière un NAT, lorsque le routeur ne maintient le mapping UDP ouvert que pendant le passage de paquets. Lorsque ce mapping expire, le serveur ne peut plus joindre le client. PersistentKeepalive = 25 maintient le mapping ouvert. Configurez-le sur le client, et non sur un serveur doté d’une adresse IP publique.
DNS et la fuite que personne ne remarque
Avec AllowedIPs = 0.0.0.0/0 et sans ligne DNS =, le client conserve le resolver fourni par le réseau local, celui du routeur du café à 192.168.1.1. Cette route est plus spécifique que la route par défaut. Les requêtes DNS sortent donc en clair par la liaison locale, tandis que tout le reste passe dans le tunnel. Le trafic est privé, mais la liste des noms ne l’est pas.
Deux options correctes sont possibles. Configurez DNS avec un resolver public (DNS = 9.9.9.9). Les requêtes passent alors dans le tunnel et sortent depuis votre serveur, même si ce resolver peut toujours les consulter. Vous pouvez aussi exécuter unbound ou dnsmasq, lié à 10.8.0.1, définir DNS = 10.8.0.1, puis ajouter udp dport 53 iifname "wg0" accept à la chaîne input. Définissez cette ligne et oubliez le resolver : plus rien ne sera résolu.
Sur les clients Linux, wg-quick applique DNS via resolvconf. S’il est absent, vous obtenez resolvconf: command not found. Installez openresolv ou définissez PostUp = resolvectl dns %i 10.8.0.1 sur un client utilisant systemd-resolved.
Ajouter et supprimer des peers sans interrompre le tunnel
Redémarrer l’interface pour ajouter un utilisateur déconnecte toutes les sessions actives. Ajoutez le bloc [Peer] à wg0.conf, puis rechargez l’ensemble des peers sans interrompre le tunnel.
sudo bash -c 'wg syncconf wg0 <(wg-quick strip wg0)'wg-quick strip affiche la configuration sans les clés propres à wg-quick (Address, DNS, PostUp), et syncconf applique les différences sans interrompre les sessions actives. Cette commande met uniquement à jour les peers : une modification de Address nécessite toujours un arrêt et un redémarrage complets. Révoquez l’accès avec sudo wg set wg0 peer <public key> remove, puis supprimez le bloc du fichier, sinon il réapparaîtra lors du prochain rechargement.
Modes d’échec et messages affichés
La négociation ne s’achève jamais. wg show répertorie le pair sans latest handshake, et les journaux du client affichent :
Handshake for peer 1 (10.0.0.10:51820) did not complete after 5 seconds, retrying (try 2)Aucun paquet n’arrive ou aucun paquet n’est accepté. Vérifiez, dans l’ordre : que UDP 51820 est ouvert dans le pare-feu du VPS et dans le pare-feu réseau de votre fournisseur, qui est généralement géré séparément dans les panneaux d’administration ; que l’adresse et le port Endpoint sont corrects ; que les clés ne sont pas inversées. La clé du bloc [Peer] du client doit être la clé publique du serveur. Coller une clé privée ou la clé publique du client produit exactement ce symptôme. sudo tcpdump -ni any udp port 51820 sur le serveur indique si des paquets arrivent. Le module du noyau n’écrit rien par défaut ; les messages WireGuard apparaissent dans dmesg uniquement après l’activation du débogage dynamique (echo module wireguard +p | sudo tee /sys/kernel/debug/dynamic_debug/control). Une fois celui-ci activé, une incompatibilité de clés apparaît comme un rejet pour MAC invalide.
La négociation fonctionne, mais pas Internet. ping 10.8.0.1 aboutit, mais ping 1.1.1.1 expire : le forwarding ou le NAT manque. Vérifiez que sysctl net.ipv4.ip_forward contient 1, puis surveillez les compteurs pendant que le client envoie des pings, avec sudo nft list ruleset ou sudo iptables -t nat -L POSTROUTING -n -v. Aucun paquet sur la règle de masquerade indique que le nom de l’interface de sortie est incorrect. Un compteur qui augmente sans réponse indique un problème dans la policy de la chaîne de forwarding.
Internet fonctionne, mais pas la résolution des noms. ping 1.1.1.1 aboutit et curl https://example.com renvoie Could not resolve host. La ligne DNS est absente ou elle désigne un resolver inaccessible depuis le tunnel.
Certains sites HTTPS restent bloqués. SSH et le ping fonctionnent, mais les pages volumineuses restent bloquées. Il s’agit d’un problème de MTU de chemin : le tunnel ajoute une surcharge, et un équipement intermédiaire peut supprimer les paquets trop volumineux sans laisser revenir de message ICMP. Réduisez MTU dans le bloc [Interface] du client, essayez 1420, puis 1380, puis 1280. Si la réduction du MTU résout les blocages, mais que le débit reste décevant, ne choisissez pas des valeurs rondes au hasard. Suivez plutôt la procédure pour trouver le véritable MTU de chemin par bissection et limiter le TCP MSS. Elle permet aussi d’écarter les causes qui ne concernent pas du tout le tunnel.
L’interface refuse de démarrer. Address already in use signifie qu’un autre processus utilise UDP 51820. Cannot find device wg0 après un up échoué signifie généralement que la configuration a été rejetée ; consultez journalctl -u wg-quick@wg0 -n 50.
Migration depuis Streisand ou OpenVPN
Streisand n’est plus maintenu et son dépôt est archivé. Exécuter un VPN avec une automatisation abandonnée crée un problème de sécurité qui s’aggrave progressivement. Il n’existe pas de mise à niveau sur place, et la PKI d’OpenVPN ne se convertit pas : WireGuard n’utilise ni certificats ni CA, et ses clés n’expirent pas. Chaque client reçoit donc une nouvelle paire de clés.
Effectuez la migration en parallèle. WireGuard sur UDP 51820 peut coexister avec OpenVPN sur 1194 sur le même serveur. Déployez wg0, migrez les clients un par un, puis arrêtez l’ancien service. Le modèle d’OpenVPN fondé sur un nom d’utilisateur, un mot de passe et la révocation ne se transpose pas. Si vous avez besoin de comptes ou d’une piste d’audit, ajoutez cette couche au-dessus de WireGuard.
Sauvegardes, mises à niveau et contraintes à grande échelle
/etc/wireguard est le serveur. Sauvegardez-le (sudo tar czf wg-backup.tgz -C /etc wireguard, mode 600, stocké hors de la machine) pour pouvoir le reconstruire sur un VPS neuf en quelques minutes. Si vous perdez la clé privée du serveur, vous devez réémettre la configuration de chaque client, car les clients épinglent la clé publique du serveur. Les mises à niveau se résument à un apt upgrade standard, suivi d’un redémarrage pour les mises à jour du kernel, et wg-quick@wg0 revient automatiquement si vous l’avez activé.
L’état associé à chaque pair est réduit et la cryptographie s’exécute dans le kernel. La limite vient donc du CPU et de la bande passante autorisés par votre VPS, et non d’un élément de cette configuration. Mesurez-la avec iperf3 à travers le tunnel au lieu de vous fier à une valeur publiée. À grande échelle, ce sont les opérations qui deviennent difficiles. Chaque pair a besoin d’une adresse IP de tunnel unique. Modifier manuellement soixante blocs [Peer] est le meilleur moyen de laisser passer des doublons AllowedIPs : générez les configurations avec un script. Un serveur correspond à un endpoint UDP et à un point de défaillance uniques. WireGuard ne fournit aucun clustering : la redondance nécessite un second serveur avec ses propres clés. La rotation des clés reste manuelle. Notez donc qui détient chaque clé et comment la révoquer. Lorsque ce suivi dépasse ce qu’un fichier texte peut gérer, la solution habituelle consiste à ajouter un control plane au même data plane du kernel. Un serveur NetBird auto-hébergé gère l’attribution des adresses, la distribution des pairs et les setup keys que vous géreriez autrement manuellement. Si l’exécution de ce control plane ajoute une machine de trop, Tailscale en héberge un pour vous. Son offre gratuite couvre six utilisateurs avec un nombre illimité d’appareils, ce qui suffit pour que la plupart des parcs personnels n’aient jamais à payer. Au-delà, la facturation dépend des personnes et non des machines. Le coût réel pour un foyer ou une petite équipe dépend du nombre d’utilisateurs disposant d’identifiants, et non du nombre de pairs que vous auriez dû ajouter manuellement dans wg0.conf. Dans ce modèle, la gestion AllowedIPs du split tunnel devient la publication de vos plages privées depuis un subnet router, annoncée une seule fois depuis un VPS et approuvée de manière centralisée au lieu d’être copiée dans le fichier de chaque client. La pertinence de ce compromis dépend de ce qu’un control plane hébergé peut réellement atteindre, et il ne détient jamais les clés qui chiffrent votre trafic, même s’il détermine quels pairs peuvent se découvrir.
Tout cela nécessite une machine Linux que vous contrôlez, une adresse IP publique, un kernel dans lequel vous pouvez charger un module et un firewall dont vous maîtrisez toute la chaîne.
FAQ
Pourquoi la négociation WireGuard n’aboutit-elle jamais ?
wg show afficher un peer sans latest handshake signifie que les paquets n’arrivent pas ou ne sont pas acceptés. Vérifiez UDP 51820 dans le pare-feu du VPS et dans le pare-feu réseau distinct de votre fournisseur. Vérifiez le host et le port Endpoint, puis contrôlez que les clés ne sont pas inversées : le bloc [Peer] du client doit contenir la clé publique du serveur. sudo tcpdump -ni any udp port 51820 sur le serveur indique si des paquets arrivent. dmesg ne signale les échecs de négociation WireGuard qu’après l’activation du débogage dynamique (echo module wireguard +p | sudo tee /sys/kernel/debug/dynamic_debug/control). Une clé incorrecte apparaît alors sous la forme d’un paquet rejeté pour MAC invalide.
Le tunnel se connecte, mais je n’ai pas accès à Internet. Que manque-t-il ?
Le fait que ping 10.8.0.1 fonctionne alors que ping 1.1.1.1 expire indique un problème de forwarding ou de NAT. Vérifiez que sysctl net.ipv4.ip_forward contient 1 et que cette valeur est définie dans /etc/sysctl.d/, pas uniquement avec un sysctl -w qui disparaît au redémarrage. Vérifiez ensuite que la règle de masquerade utilise le nom de votre véritable interface de sortie, obtenu avec ip route show default, enp1s0 ou, plus rarement, ens3 ou eth0.
La ligne DNS = est-elle nécessaire dans la configuration du client ?
Avec un tunnel complet et sans ligne DNS =, le client conserve le resolver appris sur le réseau local. Ces requêtes circulent alors en clair sur la liaison locale, tandis que tout le reste passe dans le tunnel. Configurez DNS avec un resolver public, ou exécutez unbound/dnsmasq en l’attachant à 10.8.0.1, puis ouvrez udp dport 53 iifname "wg0" dans la chaîne input.
Que contrôle réellement AllowedIPs ?
Ce paramètre a deux fonctions. En sortie, il s’agit d’une table de routage : le trafic correspondant à AllowedIPs d’un peer est chiffré et envoyé à ce peer. En entrée, il s’agit d’une liste de contrôle d’accès : un paquet déchiffré dont la source ne fait pas partie de AllowedIPs de ce peer est rejeté. C’est pourquoi le serveur indique un /32 par client, tandis que le client peut indiquer 0.0.0.0/0.
WireGuard fonctionne-t-il sur n’importe quel VPS ?
Sur un VPS KVM, il fonctionne avec le module intégré au noyau, sans configuration supplémentaire. Avec une virtualisation par conteneurs qui partage le noyau de l’hôte, comme OpenVZ ou LXC, modprobe wireguard échoue avec Operation not supported. Il faut alors utiliser l’implémentation userspace wireguard-go. Exécutez sudo modprobe wireguard && echo ok avant toute autre commande.