WireGuard : cloisonner son infra et ne plus exposer aucun port critique sur Internet

DevOps Docker Infrastructure Sécurité VPN WireGuard
WireGuard : cloisonner son infra et ne plus exposer aucun port critique sur Internet

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.

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.

Retour aux articles DevOps