Installer wg-easy avec WireGuard et Docker Compose
Déployez WireGuard avec l’interface web wg-easy dans Docker Compose : ports, NET_ADMIN, sysctls requis et codes QR pour connecter vos téléphones.
Ce que vous allez créer
wg-easy est WireGuard avec une interface web, exécuté dans un conteneur Docker unique. Il gère l’interface WireGuard pour vous et ajoute une interface web permettant de créer des clients. Chaque client créé reçoit un fichier de configuration et un code QR. Un téléphone peut donc rejoindre le VPN en visant l’écran avec son appareil photo.
Le tunnel lui-même est un WireGuard standard. Le module du noyau transporte les paquets, le débit est donc identique à celui d’une configuration écrite manuellement. Vous gagnez la gestion du cycle de vie des clients : ajout, désactivation et suppression de peers sans modifier un fichier de configuration via SSH. En contrepartie, vous perdez le contrôle direct de ce fichier. C’est le sujet de la configuration manuelle de WireGuard sur un VPS.
Vous avez besoin d’un VPS KVM avec une adresse IPv4 publique, de Docker Engine avec le plugin Compose et d’un accès root. La virtualisation de conteneurs qui partage le noyau de l’hôte, comme OpenVZ ou LXC, ne peut généralement pas charger le module WireGuard. Le conteneur échoue alors à démarrer l’interface.
La version 15 a déplacé les paramètres hors de l’environnement
La plupart des guides que vous trouverez ont été écrits pour wg-easy 14. Vous y définissez WG_HOST avec l’adresse de votre serveur et PASSWORD_HASH avec un hash bcrypt du mot de passe administrateur, tous deux comme variables d’environnement. La version 15 est une réécriture. Les notes de migration officielles indiquent clairement que v15 n’utilise pas les mêmes variables d’environnement que v14 et que la plupart d’entre elles ont été déplacées vers le panneau d’administration de l’interface web.
WG_HOST et PASSWORD_HASH n’ont donc plus aucun effet. Si vous copiez un ancien fichier compose, le conteneur démarre, ignore ces lignes, puis vous demande de créer un compte administrateur dans le navigateur. Ce n’est pas un bug. C’est le nouveau processus de configuration.
En juillet 2026, le tag majeur à utiliser est 15. Épinglez la version majeure au lieu d’utiliser latest, car une mise à niveau majeure modifie le format de configuration sur disque et ne permet pas un retour arrière propre.
Le fichier Compose
Créez un répertoire pour la stack et écrivez-y le fichier Compose officiel. Il s’agit du fichier fourni par l’éditeur, sans modification.
sudo mkdir -p /etc/docker/containers/wg-easy
sudo curl -o /etc/docker/containers/wg-easy/docker-compose.yml \
https://raw.githubusercontent.com/wg-easy/wg-easy/master/docker-compose.ymlSon contenu ressemble à ceci :
volumes:
etc_wireguard:
services:
wg-easy:
image: ghcr.io/wg-easy/wg-easy:15
container_name: wg-easy
networks:
wg:
ipv4_address: 10.42.42.42
ipv6_address: fdcc:ad94:bacf:61a3::2a
volumes:
- etc_wireguard:/etc/wireguard
- /lib/modules:/lib/modules:ro
ports:
- "51820:51820/udp"
- "51821:51821/tcp"
restart: unless-stopped
cap_add:
- NET_ADMIN
- SYS_MODULE
sysctls:
- net.ipv4.ip_forward=1
- net.ipv4.conf.all.src_valid_mark=1
- net.ipv6.conf.all.disable_ipv6=0
- net.ipv6.conf.all.forwarding=1
- net.ipv6.conf.default.forwarding=1
networks:
wg:
driver: bridge
enable_ipv6: true
ipam:
driver: default
config:
- subnet: 10.42.42.0/24
- subnet: fdcc:ad94:bacf:61a3::/64etc_wireguard est un volume nommé qui contient la clé du serveur et tous les clients que vous créez. Sauvegardez ce volume. Sinon, une reconstruction supprime tous vos peers. Si vous préférez voir ces fichiers dans le système de fichiers de l’hôte, remplacez-le par un bind mount et lisez la différence entre les bind mounts et les volumes nommés avant de le faire, car les permissions se comportent différemment.
Pourquoi le conteneur a besoin de NET_ADMIN, SYS_MODULE et des sysctls
Un conteneur ne peut pas modifier la pile réseau par défaut. Chacune de ces lignes supprime un blocage précis.
NET_ADMIN permet au conteneur de créer l’interface wg0, de lui attribuer une adresse et d’écrire des routes. Sans cette capacité, le conteneur démarre puis s’arrête pendant l’activation de l’interface, car ip link add wg0 type wireguard renvoie Operation not permitted.
SYS_MODULE, avec le montage en lecture seule de /lib/modules, permet au conteneur de charger le module du noyau WireGuard si l’hôte ne l’a pas déjà chargé. Le module se trouve dans le noyau de l’hôte, et non dans l’image. Le répertoire de l’hôte doit donc être visible. Avec un noyau récent, le module est généralement intégré. Vous pouvez le confirmer avec sudo modprobe wireguard && echo ok sur l’hôte.
net.ipv4.ip_forward=1 demande au noyau de transférer les paquets qui ne sont pas destinés à la machine elle-même. Sans ce paramètre, un client se connecte, le handshake réussit, puis chaque paquet destiné à Internet est supprimé. ping 1.1.1.1 finit donc par expirer alors que le VPN semble connecté.
net.ipv4.conf.all.src_valid_mark=1 est le paramètre qui surprend le plus. WireGuard marque ses propres paquets sortants afin qu’ils ne soient pas routés de nouveau dans le tunnel. Le filtrage strict du chemin retour voit un paquet dont l’adresse source ne correspond pas à la route attendue et le supprime. Ce sysctl demande au noyau d’accepter les paquets marqués. C’est ce qui empêche un tunnel complet de se bloquer lui-même.
Démarrer le service et créer le compte administrateur
cd /etc/docker/containers/wg-easy
sudo docker compose up -d
sudo docker compose logs -fUtilisez docker compose up et docker compose down, pas start et stop. La documentation upstream avertit que l’utilisation de start sur un conteneur créé avec des paramètres différents laisse le réseau dans un état incohérent. Si vous voulez que la stack redémarre après un reboot, restart: unless-stopped le prend déjà en charge. Le comportement au démarrage des services Compose explique ce que cette policy garantit ou non.
L’interface web écoute sur TCP 51821. Lors de la première visite, elle affiche une page de configuration. Vous y créez le compte administrateur et confirmez l’adresse de l’hôte que les clients utiliseront pour joindre le serveur. Cette adresse est ensuite inscrite dans la ligne Endpoint de chaque configuration client. Il doit donc s’agir de l’IP publique ou du nom DNS du VPS. Si l’adresse est incorrecte, le QR code que vous transmettez à un téléphone pointe vers une adresse inaccessible et le handshake n’aboutit jamais.
Un dernier point concernant ce port : wg-easy 15 refuse le HTTP simple, sauf si vous définissez INSECURE=true. L’accès en HTTPS avec un certificat non approuvé convient. La terminaison TLS par un reverse proxy placé devant le service convient également. En revanche, l’accès via http:// avec les paramètres par défaut ne fonctionne pas.
Ne publiez pas le port de l’interface d’administration sur Internet
Le fichier Compose publie le port 51821 sur toutes les interfaces. Il s’agit d’une page de connexion pour une machine qui peut acheminer votre trafic. Ce port ne doit pas être exposé à Internet. La publication d’un port dans Docker ajoute des règles dans la chaîne DOCKER, qui est évaluée avant ufw. Une règle de refus ufw ne suffit donc pas à le fermer. Ce piège mérite d’être compris séparément. Pourquoi les ports publiés par Docker ignorent ufw l’explique en détail.
La solution simple consiste à lier l’interface d’administration à loopback et à y accéder via un tunnel SSH :
ports:
- "51820:51820/udp"
- "127.0.0.1:51821:51821/tcp"
environment:
- INSECURE=truePuis, depuis votre ordinateur portable :
ssh -L 51821:127.0.0.1:51821 youruser@your.server.addressOuvrez http://127.0.0.1:51821 dans le navigateur de votre ordinateur portable. Le trafic est chiffré par SSH. Le port ne répond à aucun autre client. INSECURE=true est sûr ici, car la connexion HTTP en clair ne quitte jamais l’interface loopback.
Ouvrez UDP 51820 et vérifiez les deux firewalls
WireGuard doit pouvoir recevoir des connexions UDP sur le port 51820 depuis Internet. Docker publie ce port, mais de nombreux fournisseurs placent un firewall réseau distinct devant le VPS, que Docker ne connaît pas. Ouvrez le port aux deux endroits. Si vous gérez le firewall de l’hôte avec ufw, les règles ufw de base pour un VPS sont plus simples que l’écriture manuelle de règles nftables.
Vérifiez que le conteneur est bien en écoute :
sudo ss -ulnp | grep 51820Vous devez voir un socket UDP en écoute. Si cette ligne n’apparaît pas, le conteneur n’a jamais activé l’interface, et sudo docker compose logs wg-easy indiquera la raison.
Créer un client et le scanner sur un téléphone
Dans l’interface, créez un client et donnez-lui un nom que vous reconnaîtrez plus tard, par exemple celui de l’appareil auquel il appartient. wg-easy attribue la prochaine adresse de tunnel disponible et génère la paire de clés pour vous. Chaque ligne de client propose un code QR et un fichier .conf téléchargeable.
Installez l’application officielle WireGuard sur le téléphone, choisissez d’ajouter un tunnel à partir d’un code QR, puis dirigez l’appareil photo vers le code affiché à l’écran. Le tunnel apparaît avec le nom que vous avez saisi. Activez-le. La ligne du client dans l’interface commence alors à afficher les compteurs de transfert et l’heure de la dernière authentification.
Un client qui n’affiche aucune authentification après son activation n’atteint pas du tout le serveur. Le problème vient probablement de UDP 51820, soit au niveau du pare-feu du fournisseur, soit au niveau de l’adresse du endpoint intégrée à la configuration. Un client qui affiche une authentification, mais dont l’accès à Internet ne fonctionne pas, présente plutôt un problème de forwarding ou de DNS.
Sur un ordinateur, téléchargez le fichier .conf et importez-le dans le client WireGuard au lieu de le saisir à nouveau. La clé privée de ce fichier est générée une seule fois et affichée une seule fois. Protégez ce fichier comme vous protégeriez une clé privée SSH.
Quand dépasser l’interface
wg-easy est l’outil adapté tant que vos pairs sont des personnes et des téléphones. L’interface est plus rapide que la modification de fichiers de configuration, et la révocation d’un téléphone perdu se fait en un clic.
Vous atteindrez ses limites lorsque vous voudrez configurer un élément que l’interface ne prend pas en charge. Le routage site à site, où le AllowedIPs d’un pair couvre tout un sous-réseau distant plutôt qu’une seule adresse, constitue généralement le premier obstacle. Les tunnels partagés avec des règles de routage par pair, ou une configuration générée par votre outil de provisioning, viennent ensuite. À ce stade, la configuration manuelle n’est pas plus difficile, elle est simplement différente, et le guide WireGuard classique montre comment créer le même tunnel à partir de wg0.conf. Si vous préférez ne plus exécuter du tout le plan de contrôle, WireGuard comparé à Tailscale présente l’option gérée.
Si la syntaxe Compose ci-dessus était la partie qui vous était inconnue, plutôt que WireGuard, les bases de Docker Compose sur un VPS explique le format de fichier et les commandes courantes.
FAQ
Pourquoi wg-easy ignore-t-il mes variables WG_HOST et PASSWORD_HASH ?
Ces variables appartiennent à wg-easy 14. La version 15 a été réécrite, et le projet amont a déplacé presque toute la configuration dans le panneau d’administration de l’interface web. Le conteneur ne lit aucune de ces variables. Il démarre donc normalement, puis vous demande de créer un compte d’administration lors de la première visite. Définissez l’adresse host utilisée par les clients sur cette page de configuration.
Ai-je besoin de SYS_MODULE si mon kernel intègre déjà WireGuard ?
Non. SYS_MODULE et le montage /lib/modules permettent au conteneur de charger le module lorsque le host ne l’a pas. Sur un host où sudo modprobe wireguard réussit déjà, cette capacité n’est pas utilisée. Le supprimer constitue une mesure de durcissement raisonnable, et NET_ADMIN reste requis dans tous les cas.
Le client se connecte, mais il n’y a pas d’accès à Internet. Quel est le problème ?
Une négociation sans trafic indique presque toujours un problème de forwarding. Vérifiez que net.ipv4.ip_forward=1 et net.ipv4.conf.all.src_valid_mark=1 figurent toujours dans le fichier compose, car une copie modifiée manuellement les perd souvent. Si le forwarding est activé, vérifiez le serveur DNS reçu par le client. Un tunnel qui envoie tout le trafic via le VPN, mais qui utilise un serveur DNS désormais inaccessible, ressemble exactement à une connexion inactive dans un navigateur.
Comment sauvegarder mes clients ?
Tout se trouve dans le volume nommé etc_wireguard, dans un fichier wg0.json. L’interface dispose également d’un bouton de sauvegarde qui exporte les mêmes données. Copiez ce fichier ailleurs que sur le serveur avant toute mise à niveau. La restauration consiste à importer le fichier pendant l’étape de configuration d’un conteneur vierge.
Puis-je exécuter wg-easy derrière un reverse proxy ?
Oui. Placez le proxy devant le port TCP 51821, terminez TLS à cet endroit, puis définissez INSECURE=true sur le conteneur afin qu’il accepte la liaison HTTP non chiffrée provenant du proxy. Publiez directement UDP 51820, car le trafic VPN utilise UDP et ne passe pas par un proxy HTTP.