Héberger un VPN WireGuard sur votre VPS Linux
Configurez WireGuard sur un VPS Linux avec wg0.conf, les clés, le forwarding, le NAT, les DNS et les erreurs de handshake les plus courantes.
Ce que vous allez mettre en place
Un VPN WireGuard sur un serveur que vous contrôlez tient dans 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. La majeure partie 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 kernel et présent dans le noyau principal depuis Linux 5.6. Ubuntu 24.04 et Debian 13 l’incluent donc sans module externe. Il n’y a ni négociation de chiffrements, 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é est autorisée à utiliser. Un paquet dont la vérification MAC échoue est rejeté 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 sans configuration particulière. 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. Il faut alors utiliser l’implémentation userspace wireguard-go. Commencez par vérifier avec sudo modprobe wireguard && echo ok.
Générez les clés sans les divulguer
Un /etc/wireguard/server.key lisible par tout le monde revient à ne pas avoir de VPN. 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 du client de la même manière. wg genpsk ajoute une clé prépartagée facultative, avec 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 en cours.
Transformer le boîtier en routeur
Un serveur Linux ignore les paquets qui ne lui sont pas destinés. Le forwarding et le NAT source sont tous 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 sysctl -w minimal fonctionne jusqu’au prochain redémarrage, puis cesse silencieusement de fonctionner. Le NAT a besoin de l’interface egress, c’est-à-dire la carte réseau qui donne accès à Internet, et non de wg0. Ne partez pas du principe qu’il s’agit de eth0 ; récupérez la valeur correspondante dans ip route show default, car les images actuelles utilisent des noms comme enp1s0 ou ens3.
Firewall: the port, and the forward path
One nftables file covers filter and NAT. Write /etc/nftables.conf, it flushes the existing ruleset, so skip this on a box already managed by ufw or 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
}
}Apply it with sudo systemctl enable --now nftables, keeping a second SSH session open: policy drop plus a typo in the SSH rule locks you out of your own server. Note what the forward chain does not allow, wg0 to wg0. Peers reach the internet, not each other; add iifname "wg0" oifname "wg0" accept for a peer-to-peer VPN. The same chain governs what a peer may touch on the server itself, which matters when the box doubles as a remote development box running Claude Code in tmux and you would rather not expose that side of it publicly.
On a ufw box: ufw allow 51820/udp, DEFAULT_FORWARD_POLICY="ACCEPT" in /etc/default/ufw, and a *nat POSTROUTING MASQUERADE rule at the top of /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 nécessitent des redémarrages. Une unité qui ne redémarre pas après l’un de ces redémarrages reste silencieuse jusqu’à ce qu’une personne tente de se connecter. Un drop-in OnFailure= sur wg-quick@wg0, configuré pour votre propre serveur ntfy, est donc le moyen le plus simple de recevoir l’alerte sur votre téléphone plutôt que de l’apprendre par un utilisateur bloqué à l’extérieur.
La configuration client et le paramètre que tout le monde configure 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 a deux fonctions distinctes, et les confondre est la principale source de confusion avec WireGuard.
En sortie, c’est une table de routage. Un paquet dont la destination correspond à AllowedIPs d’un peer est chiffré et envoyé à ce peer. 0.0.0.0/0, ::/0 achemine 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 restreinte : AllowedIPs = 10.8.0.0/24, 10.20.0.0/16 achemine 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 restreinte permet de garder certains services complètement hors de l’Internet public, par exemple une instance Nextcloud privée sur un VPS liée à l’adresse du tunnel, ou les VM du laboratoire de nested virtualisation exécutées sur le même serveur, accessibles aux peers tout en restant 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 AllowedIPs de ce peer est abandonné. C’est pourquoi le serveur indique 10.8.0.2/32 pour le laptop : une entrée 0.0.0.0/0 à cet endroit permettrait à ce client d’usurper n’importe quelle adresse du tunnel.
PersistentKeepalive est destiné aux clients situés derrière un NAT, lorsque le routeur ne maintient la translation UDP ouverte que pendant la circulation de paquets. À son expiration, le serveur ne peut plus joindre le client. PersistentKeepalive = 25 maintient cette translation ouverte. Configurez-le sur le client, pas 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, en l’occurrence le 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 voir. Vous pouvez aussi exécuter unbound ou dnsmasq en l’attachant à 10.8.0.1, définir DNS = 10.8.0.1 et ajouter udp dport 53 iifname "wg0" accept à la chaîne input. Définissez cette ligne, puis 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 pairs sans interrompre le tunnel
Redémarrer l’interface pour ajouter un utilisateur déconnecte toutes les personnes connectées. Ajoutez le bloc [Peer] à wg0.conf, puis rechargez l’ensemble des pairs sans interrompre l’interface.
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 la différence sans interrompre les sessions actives. Cette commande ne met à jour que les pairs : toute 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 sera rétabli lors du prochain rechargement.
Modes d’échec et messages affichés
La négociation ne se termine jamais. wg show affiche le pair sans latest handshake, et les journaux du client contiennent :
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 : UDP 51820 est-il ouvert sur le pare-feu du VPS et sur le pare-feu réseau de votre fournisseur, qui est une commande distincte dans la plupart des panels ; l’adresse et le port Endpoint sont-ils corrects ; les clés sont-elles inversées. La clé du bloc [Peer] du client doit être la clé publique du serveur, et inversement. Coller une clé privée ou la propre clé publique d’un 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 dans les journaux par défaut. Les messages WireGuard apparaissent dans dmesg seulement après l’activation du dynamic debug (echo module wireguard +p | sudo tee /sys/kernel/debug/dynamic_debug/control). Une fois activé, une incompatibilité de clé apparaît comme un rejet pour MAC invalide.
La négociation fonctionne, mais pas Internet. ping 10.8.0.1 réussit, mais ping 1.1.1.1 expire : le forwarding ou le NAT est absent. Vérifiez que sysctl net.ipv4.ip_forward contient 1, puis surveillez les compteurs pendant que le client envoie des ping, avec sudo nft list ruleset ou sudo iptables -t nat -L POSTROUTING -n -v. Aucun paquet sur la règle de masquerade signifie que le nom de l’interface de sortie est incorrect. Un compteur qui augmente sans réponse indique un problème de policy dans la chaîne forward.
Internet fonctionne, mais pas les noms. ping 1.1.1.1 réussit et curl https://example.com renvoie Could not resolve host. La ligne DNS est absente ou désigne un resolver inaccessible depuis le tunnel.
Certains sites HTTPS se bloquent. SSH et 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 élément du réseau intermédiaire rejette les paquets trop volumineux sans qu’un message ICMP ne revienne. Réduisez MTU dans [Interface] du client, essayez 1420, puis 1380 et enfin 1280. Si la réduction de la MTU supprime les blocages, mais que le débit reste insuffisant, ne choisissez pas des valeurs rondes au hasard. Suivez plutôt la recherche par dichotomie de la véritable MTU de chemin et le clamping du MSS TCP. Cette méthode permet aussi d’écarter les causes qui ne viennent pas du 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 l’échec de up 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 a été archivé. Exécuter un VPN avec une automatisation abandonnée crée un problème de sécurité qui s’aggrave lentement. Il n’existe pas de mise à niveau sur place. La PKI d’OpenVPN ne se convertit pas : WireGuard n’utilise ni certificats, ni CA, ni expiration. 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 de comptes utilisateur/mot de passe et de révocation d’OpenVPN ne peut pas être repris. 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, avec les permissions 600, et stockée hors de la machine) pour pouvoir le reconstruire sur un nouveau VPS en quelques minutes. Si vous perdez la clé privée du serveur, vous devez réémettre la configuration de chaque client, car les clients vérifient la clé publique du serveur. Les mises à niveau consistent généralement en un apt upgrade, suivi d’un redémarrage pour les mises à jour du noyau. wg-quick@wg0 revient automatiquement si vous l’avez activé.
L’état de chaque pair est réduit et la cryptographie s’exécute dans le noyau. La limite vient donc du CPU et de la bande passante de votre VPS, pas 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 doit avoir une adresse IP de tunnel unique. Modifier manuellement soixante blocs [Peer] est la meilleure façon de laisser apparaître 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 pas de clustering. La redondance nécessite donc un second serveur avec ses propres clés. La rotation des clés reste manuelle. Notez donc qui détient chaque clé et comment en révoquer une. Lorsque cette gestion dépasse ce qu’un fichier texte peut contenir, la solution habituelle consiste à ajouter un control plane au même data plane du noyau. Un serveur NetBird auto-hébergé gère l’allocation des adresses, la distribution des pairs et les setup keys que vous devriez sinon configurer manuellement. Si gérer vous-même 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 à la plupart des parcs personnels, sans abonnement. Au-delà, la facturation dépend des personnes plutôt que des machines. Le coût réel pour un foyer ou une petite équipe dépend donc du nombre de personnes disposant d’un compte, et non du nombre de pairs que vous auriez dû ajouter manuellement dans wg0.conf. Dans ce modèle, la gestion des 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. L’intérêt de cette approche dépend de ce qu’un control plane hébergé peut réellement atteindre. Il ne détient jamais les clés qui chiffrent votre trafic, mais il décide quels pairs peuvent se découvrir.
Tout cela nécessite une machine Linux que vous contrôlez, une adresse IP publique, un noyau dans lequel vous pouvez charger un module et un firewall que vous gérez de bout en bout.
FAQ
Pourquoi le handshake WireGuard ne se termine-t-il jamais ?
wg show qui liste un peer sans latest handshake signifie que les paquets n’arrivent pas ou ne sont pas acceptés. Vérifiez UDP 51820 dans le firewall du VPS et dans le firewall réseau distinct de votre fournisseur, confirmez l’hôte et le port Endpoint, puis vérifiez 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 les paquets arrivent. dmesg ne signale les échecs de handshake de WireGuard qu’après l’activation du debug dynamique (echo module wireguard +p | sudo tee /sys/kernel/debug/dynamic_debug/control). Une clé incorrecte apparaît alors comme 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 qu’il est défini dans /etc/sysctl.d/, et pas uniquement avec un sysctl -w qui disparaît au redémarrage. Vérifiez ensuite que la règle de masquerade utilise le nom réel de votre interface de sortie, obtenu avec ip route show default, enp1s0 ou, plus rarement, ens3. eth0
Ai-je besoin de la ligne DNS = dans la configuration du client ?
Avec un full tunnel et sans ligne DNS =, le client conserve le resolver appris sur le réseau local. Ces requêtes sortent 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 lié à 10.8.0.1 et ouvrez udp dport 53 iifname "wg0" dans la chaîne input.
Que contrôle réellement AllowedIPs ?
Cette directive a deux fonctions. En sortie, elle constitue une table de routage : le trafic correspondant à AllowedIPs d’un peer est chiffré et envoyé à ce peer. En entrée, elle constitue une liste de contrôle d’accès : un paquet déchiffré dont la source est en dehors 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 in-kernel et ne nécessite aucune configuration supplémentaire. Avec une virtualisation par conteneurs qui partage le kernel de l’hôte, comme OpenVZ ou LXC, modprobe wireguard échoue avec Operation not supported. La solution de repli est l’implémentation userspace wireguard-go. Exécutez sudo modprobe wireguard && echo ok avant toute autre opération.