Héberger un VPN WireGuard sur votre VPS
Installez WireGuard sur votre VPS Linux : génération des clés, wg0.conf, transfert IP, NAT, sémantique d'AllowedIPs, DNS et les échecs de handshake.
Ce que vous allez construire
Un VPN WireGuard sur un serveur que vous possédez représente environ quarante lignes de configuration : une paire de clés, un fichier d'interface, un sysctl, une règle NAT, une ouverture dans le pare-feu. L'installation est triviale, donc l'essentiel de ce guide couvre ce qui casse : les permissions des clés, AllowedIPs, le transfert et le DNS.
WireGuard est un tunnel de couche 3 dans le noyau, intégré à la branche principale depuis Linux 5.6, donc Ubuntu 24.04 et Debian 13 le fournissent sans module externe. Pas de négociation de chiffrement, pas d'autorité de certification, pas d'étape identifiant/mot de passe : un pair est une clé publique plus les adresses IP que cette clé a le droit d'utiliser. Un paquet qui échoue à sa vérification MAC est rejeté sans réponse, donc le port ne répond pas aux scans. Le revers : aucun serveur d'authentification n'existe, donc retirer un accès signifie supprimer un pair sur la machine.
Vérifiez d'abord la virtualisation
WireGuard a besoin d'un noyau dans lequel vous pouvez charger un module, et sur un VPS KVM il fonctionne immédiatement. Sur une virtualisation par conteneur qui partage le noyau de l'hôte (OpenVZ, LXC), la première commande échoue avec RTNETLINK answers: Operation not supported, et la solution de repli est l'implémentation en espace utilisateur wireguard-go. Vérifiez d'abord avec sudo modprobe wireguard && echo ok.
Générer les clés sans les divulguer
Un fichier /etc/wireguard/server.key lisible par tout le monde équivaut à l'absence totale de VPN. La ligne courante umask 077 && wg genkey | sudo tee ... n'est pas fiable, car sudo applique son propre umask au fichier que tee crée. Définissez le mode explicitement.
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 façon. wg genpsk ajoute une clé pré-partagée optionnelle, une ligne dans chaque configuration.
L'interface 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/32Appliquez-lui chmod 600 ; un avertissement au démarrage indiquant que le fichier est accessible à tous signifie que vous avez sauté cette étape. Address est l'adresse du serveur à l'intérieur du tunnel, portant le masque de tout le sous-réseau du VPN. Choisissez une plage que vous ne croiserez pas dans la nature : 192.168.1.0/24 entre en collision 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 côté serveur est un /32, l'unique adresse de tunnel que ce client possède. Donnez à deux pairs la même IP autorisée et elle bascule vers celui configuré en dernier, et le premier cesse de recevoir du trafic sans qu'aucune erreur ne s'affiche nulle part. Laissez SaveConfig non défini, sinon wg-quick down réécrit ce fichier à partir de l'état actif.
Transformer la machine en routeur
Un serveur Linux rejette les paquets qui ne lui sont pas adressés. Le transfert et le NAT source sont tous deux absents 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 silencieusement de fonctionner. Le NAT a besoin de l'interface de sortie, la carte réseau qui atteint internet, pas wg0. Ne supposez pas eth0 ; prenez la vôtre depuis 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 ; il vide le jeu de règles existant, donc sautez cette étape sur une machine déjà gérée 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 plus une faute de frappe dans la règle SSH vous enferme dehors de votre propre serveur. Notez ce que la chaîne forward n'autorise pas : de wg0 vers wg0. Les pairs atteignent internet, pas les autres pairs ; ajoutez iifname "wg0" oifname "wg0" accept pour un VPN pair-à-pair. La même chaîne régit ce qu'un pair peut toucher sur le serveur lui-même, ce qui compte quand 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 ce côté-là.
Sur une machine ufw : ufw allow 51820/udp, DEFAULT_FORWARD_POLICY="ACCEPT" dans /etc/default/ufw, et une règle *nat POSTROUTING MASQUERADE en haut de /etc/ufw/before.rules.
Le démarrer sous 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 d'AllowedIPs. enable --now est la moitié importante : un wg-quick up wg0 lancé à la main disparaît après le prochain redémarrage, et les mises à jour du noyau impliquent des redémarrages.
La configuration client, et le réglage que tout le monde rate
[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 à la fois, et les confondre est la source de la plupart des confusions autour de WireGuard.
En sortie, c'est une table de routage. Un paquet dont la destination correspond au AllowedIPs d'un pair est chiffré et envoyé à ce pair. 0.0.0.0/0, ::/0 envoie tout dans le tunnel : un tunnel complet, le serveur comme route par défaut. Un tunnel divisé est une liste plus étroite : AllowedIPs = 10.8.0.0/24, 10.20.0.0/16 transporte le trafic VPN plus un réseau privé derrière le serveur, et tout le reste garde sa route locale. Cette liste étroite est ce qui vous permet de garder des services entièrement hors de l'internet public : une instance Nextcloud privée sur un VPS liée à l'adresse du tunnel, ou les VM de laboratoire en virtualisation imbriquée tournant sur la même machine, restent accessibles aux pairs et invisibles pour tous les autres.
En entrée, c'est une liste de contrôle d'accès. Un paquet déchiffré provenant d'un pair dont l'adresse source n'est pas dans le AllowedIPs de ce pair est rejeté. C'est pourquoi le serveur indique 10.8.0.2/32 pour l'ordinateur portable : une entrée 0.0.0.0/0 à cet endroit laisserait ce client usurper n'importe quelle adresse du tunnel.
PersistentKeepalive est destiné aux clients derrière un NAT, où le routeur maintient la correspondance UDP ouverte uniquement tant que des paquets circulent. Quand elle expire, le serveur ne peut plus joindre le client. PersistentKeepalive = 25 maintient la correspondance ouverte : réglez-le sur le client, pas sur un serveur doté d'une IP publique.
Le DNS, et la fuite que personne ne remarque
Avec AllowedIPs = 0.0.0.0/0 et aucune ligne DNS =, le client conserve le résolveur qu'il a appris du réseau local : le routeur du café à 192.168.1.1. Cette route est plus spécifique que la route par défaut, donc les requêtes DNS partent en clair sur le lien local pendant que tout le reste passe par le tunnel. Le trafic est privé ; la liste des noms ne l'est pas.
Deux options honnêtes. Pointez DNS vers un résolveur public (DNS = 9.9.9.9) et les requêtes empruntent le tunnel et sortent depuis votre serveur, même si ce résolveur les voit toujours. Ou faites tourner unbound ou dnsmasq lié à 10.8.0.1, réglez DNS = 10.8.0.1, et ajoutez udp dport 53 iifname "wg0" accept à la chaîne input : réglez cette ligne et oubliez le résolveur, et plus rien ne se résout du tout.
Sur les clients Linux, wg-quick applique DNS via resolvconf ; s'il est absent, vous obtenez resolvconf: command not found. Installez openresolv, ou réglez PostUp = resolvectl dns %i 10.8.0.1 sur un client utilisant systemd-resolved.
Ajouter et retirer des pairs sans couper le tunnel
Redémarrer l'interface pour ajouter un utilisateur déconnecte tous ceux qui sont connectés. Ajoutez le bloc [Peer] à wg0.conf, puis rechargez l'ensemble des pairs sur place.
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 pendant que les sessions actives survivent. Il met à jour uniquement les pairs : un Address modifié nécessite toujours un down/up complet. Révoquez avec sudo wg set wg0 peer <public key> remove, puis supprimez le bloc du fichier, sinon il revient au prochain rechargement.
Modes de défaillance, avec les messages que vous verrez
Le handshake ne se termine jamais. wg show liste le pair sans latest handshake, et le client journalise :
Handshake for peer 1 (10.0.0.10:51820) did not complete after 5 seconds, retrying (try 2)Rien n'arrive, ou rien n'est accepté. Dans l'ordre : le port UDP 51820 est-il ouvert sur le pare-feu du VPS et sur le pare-feu réseau de votre hébergeur, un contrôle distinct sur la plupart des panneaux ; l'adresse et le port de l'Endpoint sont-ils corrects ; les clés sont-elles inversées. La clé dans le 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, donne exactement ce symptôme. sudo tcpdump -ni any udp port 51820 sur le serveur montre si les paquets arrivent tout court. Le module noyau ne journalise rien par défaut ; les messages de WireGuard n'apparaissent dans dmesg qu'après avoir activé le débogage dynamique (echo module wireguard +p | sudo tee /sys/kernel/debug/dynamic_debug/control), et avec cela activé, une clé qui ne correspond pas se manifeste par un rejet pour MAC invalide.
Le handshake fonctionne, pas d'internet. ping 10.8.0.1 réussit mais ping 1.1.1.1 expire : le transfert ou le NAT est absent. Vérifiez que sysctl net.ipv4.ip_forward affiche 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. Zéro paquet sur la règle masquerade signifie que le nom de son interface de sortie est erroné ; un compteur qui monte sans réponses pointe vers la politique de la chaîne forward.
Internet fonctionne, 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 elle désigne un résolveur inaccessible depuis l'intérieur du tunnel.
Certains sites HTTPS se figent. SSH et ping vont bien ; les grandes pages bloquent. C'est le MTU du chemin : le tunnel ajoute une surcharge, et un maillon quelconque au milieu rejette les paquets surdimensionnés sans qu'un message ICMP ne revienne. Réduisez MTU dans l'[Interface] du client : essayez 1420, puis 1380, puis 1280.
L'interface refuse de démarrer. Address already in use signifie qu'un autre processus occupe le port UDP 51820. Cannot find device wg0 après un up échoué signifie généralement que la configuration a été rejetée ; lisez journalctl -u wg-quick@wg0 -n 50.
Migrer depuis Streisand ou OpenVPN
Streisand n'est plus maintenu et son dépôt est archivé, et faire tourner un VPN sur une automatisation abandonnée est un problème de sécurité à retardement. Il n'y a pas de mise à niveau sur place, et la PKI d'OpenVPN ne se convertit pas : WireGuard n'a ni certificats, ni autorité de certification, ni expiration, donc chaque client reçoit une nouvelle paire de clés.
Migrez en parallèle : WireGuard sur le port UDP 51820 coexiste avec OpenVPN sur 1194 sur la même machine. Montez wg0, déplacez les clients un par un, puis arrêtez l'ancien service. Le modèle identifiant/mot de passe et de révocation d'OpenVPN 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 à jour, et ce qui force à grande échelle
/etc/wireguard est le serveur. Sauvegardez-le (sudo tar czf wg-backup.tgz -C /etc wireguard, mode 600, conservé hors de la machine) et vous pouvez le reconstruire sur un nouveau VPS en quelques minutes. Perdez la clé privée du serveur et chaque configuration client doit être réémise, puisque les clients épinglent la clé publique du serveur. Les mises à jour sont un apt upgrade ordinaire plus un redémarrage pour les mises à jour du noyau, et wg-quick@wg0 revient de lui-même si vous l'avez activé.
L'état par pair est réduit et la cryptographie s'exécute dans le noyau, donc le plafond est le CPU et le quota de bande passante de votre VPS plutôt que quoi que ce soit dans cette configuration : mesurez-le avec iperf3 à travers le tunnel plutôt que de vous fier à un chiffre publié. Ce qui force à grande échelle, ce sont les opérations. Chaque pair a besoin d'une IP de tunnel unique, et éditer à la main soixante blocs [Peer] est la façon dont des AllowedIPs en double se glissent : générez les configurations à partir d'un script. Un serveur est un point de terminaison UDP et un point de défaillance unique, et WireGuard n'a pas de mise en grappe : la redondance signifie un second serveur avec ses propres clés. La rotation des clés reste manuelle, donc notez qui détient quelle clé et comment vous en révoquez une.
Tout cela nécessite une machine Linux que vous contrôlez : une IP publique, un noyau dans lequel vous pouvez charger un module, et un pare-feu que vous maîtrisez de bout en bout.
FAQ
Pourquoi le handshake WireGuard ne se termine-t-il jamais ?
wg show qui liste un pair sans latest handshake signifie que les paquets n'arrivent pas ou ne sont pas acceptés. Vérifiez le port UDP 51820 à la fois sur le pare-feu du VPS et sur le pare-feu réseau distinct de votre hébergeur, confirmez l'hôte et le port de l'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 montre si les paquets arrivent tout court ; dmesg ne rapporte les échecs de handshake de WireGuard qu'après avoir activé le débogage dynamique (echo module wireguard +p | sudo tee /sys/kernel/debug/dynamic_debug/control), et alors une clé qui ne correspond pas apparaît comme un rejet pour MAC invalide.
Le tunnel se connecte mais je n'ai pas d'internet. Qu'est-ce qui manque ?
ping 10.8.0.1 qui fonctionne pendant que ping 1.1.1.1 expire pointe vers le transfert ou le NAT. Confirmez que sysctl net.ipv4.ip_forward affiche 1 et que c'est défini dans /etc/sysctl.d/, pas seulement avec un sysctl -w qui meurt au redémarrage. Vérifiez ensuite que la règle masquerade nomme votre véritable interface de sortie depuis ip route show default : enp1s0 ou ens3, rarement eth0.
Ai-je besoin de la ligne DNS = dans ma configuration client ?
Avec un tunnel complet et aucune ligne DNS =, le client conserve le résolveur qu'il a appris du réseau local, et ces requêtes partent en clair sur le lien local pendant que tout le reste passe par le tunnel. Pointez DNS vers un résolveur public, ou faites tourner 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 ?
Il remplit deux fonctions. En sortie, c'est une table de routage : le trafic correspondant au AllowedIPs d'un pair est chiffré et envoyé à ce pair. En entrée, c'est une liste de contrôle d'accès : un paquet déchiffré dont la source est hors du AllowedIPs de ce pair est rejeté. C'est pourquoi le côté serveur indique un /32 par client alors que le côté client peut indiquer 0.0.0.0/0.
WireGuard fonctionnera-t-il sur n'importe quel VPS ?
Sur un VPS KVM, il fonctionne avec le module intégré au noyau et sans configuration supplémentaire. Sur une virtualisation par conteneur qui partage le noyau de l'hôte, comme OpenVZ ou LXC, modprobe wireguard échoue avec Operation not supported et la solution de repli est l'implémentation en espace utilisateur wireguard-go. Lancez sudo modprobe wireguard && echo ok avant toute chose.