Je suis un maniaque du contrôle. Mon VPN mesh aussi.

9 min read

Un VPN mesh privé place toute votre infrastructure de développement sur le même réseau. Serveurs VPS, votre Mac, votre téléphone. Tout communique via des tunnels chiffrés au lieu d'internet ouvert. Fini d'exposer les ports SSH, les API internes, les connexions de base de données au monde extérieur. Moins de surface d'attaque, moins de risques, règles de pare-feu simplifiées.

Tailscale fait ça très bien. Le tunnel est P2P, l'installation prend 30 secondes, et on oublie que c'est là. Sauf que le serveur de coordination, ce qui permet à vos machines de se trouver, vit sur l'infrastructure de Tailscale. Et quand vous faites tourner des charges de production multi-VPS, "sur leur infrastructure" est une dépendance que vous n'aviez pas demandée.

Je me suis déjà fait avoir une fois par un fournisseur qui a changé les règles en plein milieu du jeu sur ma config de production. Une fois suffit. Alors j'ai migré vers NetBird, entièrement auto-hébergé, derrière le reverse proxy Traefik qui tourne déjà sur mon VPS. Le mesh en lui-même a pris 2 minutes par client. Le vrai boulot, c'était tout le reste.

TLDR : NetBird remplace Tailscale avec un mesh P2P WireGuard entièrement auto-hébergé. Le réseau est opérationnel en 2 minutes. Mais si vous avez déjà Traefik en production, le script d'installation officiel est inutilisable. Cet article vous donne le docker-compose personnalisé, le routage par chemin avec priorités, et les 7 pièges que ni la doc ni aucun tutoriel ne mentionnent.

Illustration comique comparant un employé de bureau submergé par les câbles versus un héros triomphant debout sur un rack de serveurs auto-hébergés avec panneau de contrôle de réseau mesh
Dépendance fournisseur vs. suprématie VPN mesh : choisissez votre propre aventure.

Tailscale Fonctionne. Ce N'est Pas le Problème.

Tailscale est vraiment un bon logiciel. Je l'ai fait tourner pendant des mois. Connexions P2P via WireGuard, traversée NAT qui marche vraiment, un panneau d'admin propre. Aucune plainte côté tunnel.

Le problème est structurel. Le serveur de coordination (Tailscale l'appelle leur "plan de contrôle") est un SaaS que vous ne possédez pas. Chaque fois que vos machines ont besoin de se découvrir, elles appellent à la maison. Si Tailscale change ses tarifs, tue l'offre gratuite, ou passe une mauvaise journée, votre réseau privé ne peut plus établir de nouvelles connexions. Les tunnels existants survivent (c'est du WireGuard en dessous), mais pas de nouveaux pairs, pas de re-génération de clés, pas de changements.

Pour un projet homelab de weekend, ça passe. Pour une infrastructure VPS de production où je fais tourner des services clients, c'est un pari que je ne veux plus faire. Pas de la paranoïa. Juste le même réflexe que tout dev développe après avoir vu un fournisseur tirer le tapis.

La solution était évidente. Auto-héberger la couche de coordination.

J'ai Regardé Toutes les Alternatives. La Plupart Sont des Impasses.

La liste courte se tue toute seule rapidement :

Headscale fait de la rétro-ingénierie du protocole Tailscale. Ça veut dire que vous dépendez toujours des apps client Tailscale, et Tailscale n'a aucune obligation de garder leur protocole stable pour un serveur tiers. La communauté a largement documenté ce risque. Votre serveur de coordination auto-hébergé peut casser à n'importe quelle mise à jour client Tailscale que vous ne contrôlez pas.

ZeroTier utilise un protocole custom, pas WireGuard. C'est rédhibitoire pour quiconque veut de la crypto standard et auditée en dessous.

Nebula (l'outil mesh de Slack) n'a pas de client iOS natif. Mon téléphone est un pair. Non-négociable.

Netmaker a des problèmes d'instabilité documentés dans les retours communauté. Je ne veux pas déboguer ma couche réseau.

WireGuard brut est solide comme un roc mais n'a pas de découverte mesh automatique. Vous gérez manuellement les configs de pairs sur chaque nœud. Avec 3 pairs c'est chiant. Avec 10 c'est un boulot. Avec 30 c'est un changement de carrière que vous n'avez pas demandé.

NetBird est celui qui a survécu à l'élimination. Entièrement open-source (client ET serveur de coordination, pas juste un côté). App iOS native sur l'App Store. WireGuard sous le capot avec une couche post-quantique Rosenpass par-dessus. Gestion d'utilisateurs locale depuis la v0.62 (pas besoin de fournisseur d'identité externe pour les petites configs). Image Docker combinée qui bundle management, signal, relay, et STUN dans un seul conteneur.

Et oui, NetBird est plus jeune que Tailscale. L'écosystème d'intégrations tierces est plus mince. Vous ne trouverez pas les mêmes connecteurs plug-and-play pour tous les outils SaaS. Mais je ne cherche pas un écosystème. Je cherche un mesh que je possède.

Le Script Officiel Est Fait pour les Gens Qui N'ont Rien en Production.

Je suis allé sur la doc NetBird. Cliqué "Self-hosting." Téléchargé getting-started.sh.

Puis je vois que le script déploie sa propre instance Traefik.

J'ai déjà Traefik v3 qui tourne sur ce VPS. Il gère le routage pour les sites clients, les apps, les tableaux de bord. Il a les certificats Let's Encrypt, les chaînes de middleware, tout le bazar. Je ne vais pas démolir ça et je ne vais pas faire tourner deux instances Traefik sur la même machine qui se battent pour le port 443.

Donc le script officiel va à la poubelle et vous vous écrivez un docker-compose personnalisé qui branche les services NetBird dans votre Traefik existant. Et c'est là que le vrai boulot commence.

Le routage est la partie délicate. Tout vit sur un seul domaine (netbird.votredomaine.com), séparé par chemin et priorité. La logique :

Priorité 100 (la plus haute) : services gRPC (signal et API management). Ceux-ci ont besoin du schéma h2c parce que Traefik termine le TLS et forward du HTTP/2 plain vers les conteneurs. Si vous utilisez http au lieu de h2c, gRPC échoue silencieusement et le client reste bloqué sur "Disconnected."

Priorité 50 : connexions WebSocket pour le service relay, endpoints API REST, et routes de callback OAuth2. HTTP standard, rien d'exotique.

Priorité 1 (catch-all) : l'UI du tableau de bord. Tout ce qui ne matche pas une règle de priorité plus haute atterrit ici.

Coturn (serveur TURN/STUN) : tourne en network_mode: host, complètement en dehors de Traefik. Il a besoin d'UDP brut sur les ports 3478 et 49152-65535. Aucun reverse proxy ne peut vous aider ici.

Architecture diagram showing Traefik as public-facing gateway (client sites, apps, NetBird dashboard) with path-based routing to NetBird containers behind it. Separate mesh overlay showing 3 peers (VPS Contabo with management server, VPS Hostinger, MacBook) connected P2P via WireGuard tunnels. Coturn bypassing Traefik via direct UDP.
Architecture Gateway Traefik avec Mesh P2P NetBird

Si vous avez déjà fait du routage Traefik, c'est un territoire familier. Si vous ne l'avez pas fait, chacun de ces niveaux de priorité m'a pris un round de "pourquoi ça fait du 502" pour bien faire.

Les 7 Pièges Que Personne Ne M'a Signalés

J'ai vieilli visiblement pendant la config du tableau de bord Dex. Quatre itérations sur l'écran de login avant que ça marche. La plupart de mes 2 heures de setup ont été passées sur cette liste, pas sur le mesh.

1. exposedAddress a besoin du port.
Dans config.yaml, le champ exposedAddress pour le serveur signal doit inclure :443. Le parser client gRPC échoue avec "missing port in address" si vous l'omettez. Le service signal reste "Disconnected" avec zéro message d'erreur utile.

2. Écran blanc après login. Pas d'erreur.
Le JavaScript du tableau de bord fait window.location.origin + redirectURI. Si votre AUTH_REDIRECT_URI est une URL complète au lieu d'un chemin relatif, ça double : https://netbird.votredomaine.com/https://netbird.votredomaine.com/nb-auth. Mettez /nb-auth, pas le domaine complet. Bonne chance pour déboguer ça sans lire le source.

3. AUTH_CLIENT_ID est "netbird-dashboard", pas "dashboard".
La config client statique Dex attend netbird-dashboard. Si vous utilisez dashboard, l'authentification échoue silencieusement. Pas d'erreur dans l'UI, pas de ligne de log utile. Vous ne pouvez juste pas vous connecter.

4. Les scopes Dex ne sont pas les scopes Auth0.
Les scopes corrects pour Dex sont openid profile email groups. Si vous copiez-collez depuis des exemples Auth0 (et beaucoup de posts de blog utilisent Auth0), vous allez ajouter api et email_verified. Ces scopes n'existent pas dans Dex. La requête de token échoue.

5. Le conteneur démarre, les logs ont l'air propres, le login ne marche pas.
Le script d'init du tableau de bord assume Auth0 par défaut. Vous avez besoin de AUTH_SUPPORTED_SCOPES=openid profile email groups et USE_AUTH0=false dans les variables d'env du tableau de bord. Sans les deux, le script crash silencieusement. Pas d'erreur, pas d'avertissement. Juste une page de login qui rejette toute tentative.

6. store.encryptionKey doit être dans config.yaml.
Sans ça, le serveur management génère une clé aléatoire au démarrage. Redémarrez le conteneur et il ne peut plus déchiffrer ses propres données. Mettez-la une fois, gardez-la pour toujours. (Ou amusez-vous à re-enregistrer tous les pairs après un reboot.)

7. Des routes qui sont clairement définies retournent 404.
Vous vérifiez la syntaxe des labels. Correcte. Vous vérifiez le chemin. Correct. Vous vérifiez la priorité. Correcte. Il s'avère que Docker Compose YAML et les heredocs shell gèrent les backticks différemment. Si vos labels Traefik utilisent des matchers de chemin délimités par backticks, enveloppez-les dans des guillemets doubles dans le YAML. Les heredocs produisent des règles qui ont l'air valides mais subtilement cassées. Énervant.

Le pattern à travers les sept : le mode d'échec est le silence. Pas de crash, pas de stack trace. Les choses ne marchent juste pas, et les logs disent que tout va bien.

2 Heures, 3 Pairs, 2ms.

L'architecture finale : serveur management sur un VPS Contabo (Ubuntu 24.04, 8GB RAM). Un conteneur netbird-server combiné qui fait tourner management, signal, relay, et STUN. Des conteneurs séparés pour netbird-dashboard et netbird-coturn. Tout derrière l'instance Traefik existante sauf Coturn.

Trois pairs sur le mesh : le VPS Contabo (Linux), un VPS Hostinger (Linux), et mon MacBook (arm64). Ping entre les deux nœuds VPS : ~2ms P2P. C'est du WireGuard direct, pas de saut relay.

La première fois que j'ai lancé ce ping et vu le nombre revenir, je suis juste resté assis là une seconde. Deux millisecondes à travers deux datacenters sur un réseau que j'ai construit moi-même. Pas de relay tiers, pas de serveur de coordination dans le cloud de quelqu'un d'autre. Juste mes boîtes qui se parlent.

J'ai failli envoyer un texto à quelqu'un à ce sujet. Puis je me suis rappelé que les gens normaux ne s'excitent pas sur les temps de ping.

Le client macOS s'installe via Homebrew. Se reconnecte proprement après mise en veille. Le client iOS est un téléchargement natif App Store, marche sur 4G/5G avec traversée NAT via Coturn quand le P2P direct n'est pas possible.

L'authentification passe par Dex embarqué avec des utilisateurs locaux. Pas de fournisseur d'identité externe, pas d'Okta, pas d'Auth0. Pour une config à une personne (ou trois personnes), les utilisateurs locaux sont tout ce dont vous avez besoin.

La migration depuis Tailscale était la partie la plus simple. Désinstaller le client Tailscale, rien ne casse côté service (vos apps ne savent pas quel VPN tourne en dessous). Mettre à jour les configs SSH pour pointer vers les nouvelles IPs mesh. Fini.

Le même VPS fait déjà tourner d'autres services auto-hébergés derrière le même proxy Traefik. Même pattern, même stack, même philosophie : si c'est critique pour votre workflow, possédez-le.

Deux heures au total. Quatre-vingt-dix minutes de celles-ci sur le cirque d'authentification du tableau de bord.

Quand le Serveur Management Meurt, les Tunnels Non.

C'est la partie qui rend toute la migration valable.

Si mon serveur management tombe, les tunnels P2P existants continuent de tourner. Les sessions WireGuard entre pairs qui se connaissent déjà survivent. Pas de nouveaux pairs qui peuvent rejoindre, pas de changements de config qui se propagent, mais le trafic de production entre machines connectées continue sans interruption. C'est une propriété de WireGuard, pas une fonctionnalité NetBird. Mais c'est une propriété que vous n'obtenez que quand la couche de coordination tourne à côté de vos autres services, pas sur le cloud de quelqu'un d'autre.

La procédure de backup est courte : fichiers de config et volumes Docker répliqués sur le second VPS. La récupération fait cinq étapes. Installer Docker, copier la config, restaurer le volume, pointer le DNS vers le serveur de backup, docker compose up. Reconnecter les clients. J'ai testé. Ça marche.

J'ai aussi déplacé le domaine management vers quelque chose de moins évident. La sécurité par l'obscurité n'est pas une stratégie, mais il n'y a pas de raison de rendre votre endpoint de coordination facilement découvrable non plus.

L'industrie va continuer à vous vendre du "zéro config" et "ça marche juste." Et ils ont raison, ça marche. Jusqu'à ce que le fournisseur change les tarifs, tue un tier, ou tombe. Puis vous découvrez que votre réseau privé dépendait d'un serveur que vous n'avez jamais possédé.

Le mesh VPN n'est pas la partie compliquée. C'est 2 minutes et 2ms. La partie compliquée est de le câbler dans une stack qui tourne déjà. Mais une fois que c'est fait, c'est à vous. Le serveur management meurt, les tunnels survivent. Vous voulez migrer, vous migrez.

Posséder votre infra, c'est être libre et sécurisé. 🤷

Sources

(*) La couverture est générée par IA. Les tunnels WireGuard, cependant, sont très réels et routent des paquets en ce moment même.

Cet article contient des liens d'affiliation. Je peux gagner une petite commission si vous achetez via eux.