Un soir j'ai fait grep "Failed password" /var/log/auth.log | wc -l sur un de mes VPS. 1 400 tentatives en 24 heures. Le serveur était en ligne depuis trois semaines. J'avais fail2ban, des clés SSH, le mot de passe root désactivé — tout ce qu'on est censé faire. Mais ça me dérangeait quand même d'avoir autant de bruit dans les logs, et surtout d'avoir des ports ouverts sur Internet sans raison valable.
Depuis que j'ai mis WireGuard en place, sur mes VPS il n'y a plus qu'un seul port ouvert publiquement : le 443. Le reste — SSH, bases de données, interfaces d'administration, agents Portainer — tourne uniquement sur l'interface VPN. Depuis Internet, il n'y a rien à attaquer. Les scans de ports ne trouvent rien. Les tentatives de brute force n'aboutissent nulle part.
Voilà comment c'est monté.
Pourquoi WireGuard plutôt qu'autre chose
J'ai utilisé OpenVPN pendant des années. Le problème d'OpenVPN, c'est la complexité opérationnelle : une PKI à gérer, des certificats à renouveler, une config en 50 lignes pour quelque chose de basique, et des performances qui plafonnent à cause de l'espace utilisateur. Un VPN qui consomme 15% de CPU à 100 Mbps, c'est rédhibitoire sur un petit VPS.
WireGuard c'est l'inverse. Le code tient en 4 000 lignes (contre 600 000 pour OpenVPN), il tourne dans le kernel depuis Linux 5.6, la cryptographie est moderne et non négociable (Curve25519, ChaCha20, Poly1305), et la configuration se résume à des clés publiques et des blocs [Peer]. Premier tunnel opérationnel en 10 minutes chrono.
La différence de performance est mesurable : sur le même VPS, WireGuard consomme 3 à 4 fois moins de CPU qu'OpenVPN pour le même débit. Sur un VPS à 2 vCPU, ça compte.
La topologie que j'utilise
Un serveur WireGuard sur un VPS qui sert de hub central. Toutes mes machines — les autres VPS et mes serveurs locaux — sont des peers qui se connectent à ce hub. Résultat : toutes les machines se voient entre elles via le réseau VPN 10.8.0.0/24, même les deux serveurs locaux qui sont derrière une box sans IP fixe.
Internet
|
| :443 uniquement (HTTPS via Traefik)
|
[ VPS 1 — Hub WireGuard ] 10.8.0.1
|
| Réseau WireGuard 10.8.0.0/24 (chiffré)
├── [ VPS 2 ] 10.8.0.2
├── [ Serveur local 1 ] 10.8.0.3
└── [ Serveur local 2 ] 10.8.0.4
Les services qui n'ont aucune raison d'être publics — Portainer, les bases PostgreSQL, les interfaces d'admin — sont configurés pour écouter uniquement sur 10.8.0.x. Même si quelqu'un scan les ports du VPS 2, il ne verra que ce qui est intentionnellement exposé.
Installer et configurer le serveur WireGuard
Sur le VPS hub (Debian/Ubuntu) :
apt update && apt install wireguard -y
# Générer les clés du serveur
wg genkey | tee /etc/wireguard/private.key | wg pubkey > /etc/wireguard/public.key
chmod 600 /etc/wireguard/private.key
Configuration du serveur dans /etc/wireguard/wg0.conf :
[Interface]
Address = 10.8.0.1/24
ListenPort = 51820
PrivateKey =
# Activer le routage IP
PostUp = iptables -A FORWARD -i wg0 -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
PostDown = iptables -D FORWARD -i wg0 -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE
# VPS 2
[Peer]
PublicKey =
AllowedIPs = 10.8.0.2/32
# Serveur local 1
[Peer]
PublicKey =
AllowedIPs = 10.8.0.3/32
# Serveur local 2
[Peer]
PublicKey =
AllowedIPs = 10.8.0.4/32
# Activer le forwarding IP
echo "net.ipv4.ip_forward=1" >> /etc/sysctl.conf
sysctl -p
# Démarrer WireGuard au boot
systemctl enable wg-quick@wg0
systemctl start wg-quick@wg0
Un point sur le port WireGuard lui-même : il utilise UDP 51820. Ce port doit être ouvert sur le hub — c'est le seul port supplémentaire à autoriser sur ce VPS en dehors du 443. Sur tous les autres VPS (les peers), même ce port n'est pas ouvert publiquement puisqu'ils se connectent au hub, pas l'inverse.
Configurer les peers
Sur chaque machine à connecter, même installation, et une configuration miroir :
wg genkey | tee /etc/wireguard/private.key | wg pubkey > /etc/wireguard/public.key
chmod 600 /etc/wireguard/private.key
/etc/wireguard/wg0.conf sur le VPS 2 (peer) :
[Interface]
Address = 10.8.0.2/24
PrivateKey =
[Peer]
PublicKey =
Endpoint = IP_PUBLIQUE_HUB:51820
AllowedIPs = 10.8.0.0/24
PersistentKeepalive = 25
Le PersistentKeepalive = 25 est important pour les serveurs locaux derrière NAT (box internet). Sans ça, la connexion WireGuard peut se couper après quelques minutes d'inactivité parce que la table NAT de la box expire. Avec le keepalive, le peer envoie un paquet toutes les 25 secondes pour maintenir la session ouverte. Pour les VPS avec IP publique fixe, c'est optionnel.
systemctl enable wg-quick@wg0
systemctl start wg-quick@wg0
# Vérifier la connexion
wg show
# Doit afficher le peer avec une "latest handshake" récente
Fermer les ports publics — la vraie sécurité
WireGuard en place, la deuxième étape c'est de s'assurer que vos services n'écoutent plus sur les interfaces publiques. Sur mes VPS, la règle est simple : seul le 443 est ouvert sur l'interface publique. Tout le reste passe par le VPN.
Avec UFW :
# Réinitialiser et repartir proprement
ufw --force reset
# Règles de base
ufw default deny incoming
ufw default allow outgoing
# Seulement ce qui est vraiment public
ufw allow 443/tcp # HTTPS (Traefik)
ufw allow 51820/udp # WireGuard (uniquement sur le hub)
# Tout autoriser depuis le réseau VPN
ufw allow in on wg0
ufw enable
Le allow in on wg0 est ce qui rend tout le reste accessible depuis le VPN : SSH, Portainer agents sur le 9001, pgAdmin, whatever. Ces services continuent de tourner normalement, mais ils ne sont joignables que depuis une machine connectée au VPN.
Pour SSH en particulier, je l'ai complètement retiré de l'interface publique :
# Écouter SSH uniquement sur l'IP VPN
# Dans /etc/ssh/sshd_config
ListenAddress 10.8.0.1 # ou 10.8.0.2 selon la machine
systemctl restart sshd
Après ça, impossible de se connecter en SSH depuis Internet. Uniquement depuis une machine sur le réseau WireGuard. Les 1 400 tentatives quotidiennes dans les logs : zéro depuis.
Exposer des services uniquement sur le VPN
Même logique pour les autres services. Portainer agent sur le VPS 2 :
docker run -d \
-p 10.8.0.2:9001:9001 \
--name portainer_agent \
--restart=always \
-v /var/run/docker.sock:/var/run/docker.sock \
-v /var/lib/docker/volumes:/var/lib/docker/volumes \
portainer/agent:latest
PostgreSQL dans un docker-compose, bind sur l'IP VPN :
services:
postgres:
image: postgres:16
ports:
- "10.8.0.2:5432:5432" # accessible uniquement via VPN
environment:
POSTGRES_PASSWORD: ${DB_PASSWORD}
Le pattern est toujours le même : VPN_IP:PORT_HOTE:PORT_CONTENEUR. Sans ça, Docker bind sur 0.0.0.0 et contourne les règles UFW — piège classique que j'ai rencontré avec une base PostgreSQL que je pensais protégée par le firewall et qui était en réalité accessible depuis Internet.
Vérifier que rien ne fuite
Après configuration, je vérifie systématiquement depuis une machine extérieure au réseau :
# Depuis une machine sans accès VPN (ex: smartphone en 4G)
nmap -p 1-65535 IP_PUBLIQUE_VPS
# Résultat attendu :
# 443/tcp open https
# Tout le reste : filtered ou closed
Et depuis le réseau VPN, les mêmes services doivent être joignables :
# Depuis une machine sur le VPN
nc -zv 10.8.0.2 9001 # Portainer agent
nc -zv 10.8.0.2 5432 # PostgreSQL
nc -zv 10.8.0.2 22 # SSH
Si quelque chose n'est pas joignable depuis le VPN alors qu'il devrait l'être, c'est généralement le bind de l'application qui est toujours sur 127.0.0.1 ou 0.0.0.0 mais bloqué par UFW — vérifier avec ss -tlnp sur la machine distante.
Pour aller plus loin : le cours vidéo, wg-easy et pourquoi WireGuard est si discret
J'ai depuis consacré un cours vidéo complet à WireGuard : l'installation simplifiée avec Docker, les raisons concrètes de sa sécurité, puis la mise en réseau de plusieurs serveurs avec le SSH caché derrière le VPN. Voici la vidéo, et les compléments qui manquaient à cet article.
L'alternative simple : wg-easy en Docker
La configuration manuelle décrite plus haut est idéale pour relier des serveurs entre eux. Mais pour donner un accès VPN à des appareils (votre téléphone, votre portable, celui d'un collègue), générer des clés et des fichiers à la main devient vite pénible. C'est là que wg-easy est très pratique : un seul conteneur qui embarque le serveur WireGuard et une interface web pour gérer les clients.
services:
wg-easy:
image: ghcr.io/wg-easy/wg-easy:15
container_name: wg-easy
restart: unless-stopped
environment:
- INSECURE=false
volumes:
- wg-easy:/etc/wireguard
- /lib/modules:/lib/modules:ro
ports:
- "51820:51820/udp" # le VPN
- "10.8.0.1:51821:51821/tcp" # l'interface web, jamais publique
cap_add:
- NET_ADMIN
- SYS_MODULE
sysctls:
- net.ipv4.ip_forward=1
- net.ipv4.conf.all.src_valid_mark=1
volumes:
wg-easy:
Deux ports : le port UDP 51820 pour le tunnel, et le port de l'interface web. La capacité NET_ADMIN permet au conteneur de créer l'interface réseau du tunnel. Notez que l'interface web n'est publiée que sur l'adresse VPN : elle n'a aucune raison d'être joignable depuis Internet.
Attention : wg-easy crée son propre réseau WireGuard. Sur une machine qui fait déjà tourner le wg0 manuel décrit plus haut, choisissez un autre port UDP et un autre sous-réseau, sinon les deux se marchent dessus. Une répartition qui fonctionne bien : la configuration manuelle pour relier les serveurs entre eux, wg-easy pour les appareils clients.
Dans l'interface, on crée un client en un clic (« Téléphone Arnaud », « Portable pro »). wg-easy génère ses clés et affiche un QR code : on le scanne avec l'application WireGuard du téléphone, et c'est connecté. Le tableau de bord montre qui est connecté et combien de données passent. Un téléphone perdu ? On désactive son client, il perd l'accès immédiatement.
La version 15 de wg-easy a beaucoup changé (configuration initiale par un assistant web, double authentification possible). Fixez la version majeure et lisez les notes de migration avant de passer d'une version à l'autre.
Pourquoi WireGuard est si sûr : trois raisons concrètes
1. Moins de code, moins de failles. Environ 4 000 lignes pour le cœur, contre des centaines de milliers pour OpenVPN ou IPsec. Un expert peut relire WireGuard en entier ; personne ne peut en dire autant des VPN historiques. Et chaque ligne de code en moins, c'est une faille potentielle en moins.
2. Une cryptographie moderne et imposée. Il n'y a aucune option de chiffrement à choisir. Pas de mauvaise configuration possible, et aucun moyen pour un attaquant de forcer la négociation vers un algorithme plus faible, une attaque classique contre les protocoles « configurables ».
3. Le silence, grâce à UDP. C'est le point que je trouve le plus élégant. Contrairement à TCP, UDP n'oblige pas un serveur à répondre pour établir une connexion. WireGuard en profite : si un paquet n'est pas signé par une clé connue, il ne répond rien du tout. Pour un scanner de ports, le port 51820 est indiscernable d'un port fermé. Impossible de savoir qu'un VPN écoute.
C'est ce qui rend la stratégie de cet article si efficace : le seul port « ouvert » en plus du 443 est un port qui reste muet face à quiconque n'a pas la bonne clé.
Un bonus pour les appareils mobiles
WireGuard ne maintient pas de « session » au sens classique : il identifie les pairs par leur clé, pas par leur adresse IP. Quand votre téléphone passe du wifi à la 4G et change d'adresse, le tunnel continue de fonctionner sans reconnexion visible. C'est ce qu'on appelle le roaming.
Avant de fermer le SSH public : l'accès de secours
Une précaution que je répète dans le cours parce qu'elle évite une très mauvaise soirée : avant de retirer le SSH de l'interface publique, vérifiez que vous avez un accès de secours. La console web (VNC/KVM) de votre hébergeur, par exemple. Si la configuration WireGuard casse après une mise à jour, c'est le seul moyen de reprendre la main sur une machine dont le SSH n'écoute plus que sur le VPN.
Et un client WireGuard par appareil, jamais une configuration partagée entre plusieurs machines : c'est ce qui permet de révoquer un seul appareil sans couper tout le monde.
Ce que ça change sur la durée
La surface d'attaque de mes VPS est passée de "plusieurs ports ouverts avec des services qui tournent dessus" à "un port 443 géré par Traefik". C'est le seul vecteur d'entrée depuis Internet, et Traefik gère le routage vers les bons services selon le nom de domaine.
Les logs auth sont silencieux. Ça paraît anodin mais mentalement c'est reposant — pas de bruit, pas de faux positifs, pas de fail2ban qui ban des IPs légitimes par erreur.
L'autre gain inattendu : la gestion des accès est beaucoup plus claire. Si quelqu'un a besoin d'accéder à un service interne, je lui crée un peer WireGuard. Si je veux révoquer l'accès, je supprime le peer du fichier de config et je recharge WireGuard. Pas de gestion de règles firewall par IP, pas d'exceptions à maintenir.
WireGuard tourne chez moi depuis plus d'un an sans intervention. Pas de certificats à renouveler, pas de service qui crash, pas de mise à jour critique urgente. C'est exactement ce qu'on veut d'une brique d'infrastructure : silencieux et fiable.