Aller au contenu
Sécurité medium

Tailscale : VPN mesh WireGuard et control plane souverain

16 min de lecture

Tailscale est un VPN mesh basé sur WireGuard : chaque machine reçoit une IP privée stable et parle aux autres en pair-à-pair chiffré, sans ouvrir un seul port sur le pare-feu. Là où un VPN classique fait transiter tout le trafic par une passerelle centrale, Tailscale crée un réseau maillé où les nœuds se joignent directement. Ce guide montre comment former ce mesh, le sécuriser avec des ACL grants zero-trust, l'étendre avec des subnet routers et des exit nodes, puis reprendre le contrôle du plan de contrôle avec Headscale, l'implémentation open source auto-hébergeable. Pour administrateurs et DevSecOps, sur Linux. Les sorties proviennent d'un lab réel Headscale + clients Tailscale.

  • Installer le client Tailscale sur Debian/Ubuntu et RHEL/Fedora
  • Former un mesh chiffré et résoudre les noms avec MagicDNS
  • Écrire une politique zero-trust avec les grants (le format 2026)
  • Exposer un LAN (subnet router) et router Internet (exit node)
  • Auto-héberger le control plane avec Headscale pour la souveraineté

Au moins deux machines Linux (physiques, VM ou conteneurs) avec un accès root, et une sortie Internet sur le port UDP 41641 (ou n'importe quel port sortant : Tailscale s'adapte). Aucun port entrant à ouvrir. Pour la partie auto-hébergée, Docker et Docker Compose suffisent.

Comment Tailscale relie des machines sans ouvrir de port

Section intitulée « Comment Tailscale relie des machines sans ouvrir de port »

Tailscale sépare deux plans. Le data plane (le transport des données) repose entièrement sur WireGuard : le chiffrement est de bout en bout et les clés privées ne quittent jamais la machine qui les génère. Le control plane (le coordination server) orchestre le reste : il authentifie les appareils via votre fournisseur d'identité (Google, Microsoft, GitHub, SSO), distribue les clés publiques, pousse la politique d'accès, et aide les nœuds à se découvrir.

Pour traverser les NAT et les pare-feu sans configuration, chaque nœud tente d'abord une connexion directe (technique de hole punching). Si elle échoue (NAT symétrique, CGNAT des réseaux mobiles), le trafic bascule sur un relais DERP. Point crucial pour la confidentialité : un relais DERP ne déchiffre rien, il relaie du trafic déjà chiffré par WireGuard. Tailscale continue de sonder un chemin direct et bascule automatiquement dès qu'il en trouve un.

Cette architecture a une conséquence de souveraineté qu'il faut comprendre. Le coordination server voit les clés publiques, l'inventaire des appareils, les identités et les métadonnées de connexion (qui parle à qui). Il ne voit jamais le contenu du trafic. Pour un usage où même ces métadonnées doivent rester sous votre juridiction, on remplace ce plan de contrôle propriétaire par Headscale, traité plus bas.

Le client s'installe via le dépôt officiel de votre distribution. La version stable à la rédaction est v1.98.

Fenêtre de terminal
curl -fsSL https://tailscale.com/install.sh | sh
sudo tailscale up

Pour un contrôle plus fin en production, ajoutez le dépôt manuellement plutôt que d'exécuter un script distant :

Fenêtre de terminal
curl -fsSL https://pkgs.tailscale.com/stable/debian/bookworm.noarmor.gpg \
| sudo tee /usr/share/keyrings/tailscale-archive-keyring.gpg >/dev/null
curl -fsSL https://pkgs.tailscale.com/stable/debian/bookworm.tailscale-keyring.list \
| sudo tee /etc/apt/sources.list.d/tailscale.list
sudo apt-get update && sudo apt-get install tailscale

La commande tailscale up ouvre une URL d'authentification à valider dans le navigateur. Pour une machine sans interface (serveur, CI), utilisez une clé d'authentification : sudo tailscale up --authkey tskey-auth-xxxx. Vérifiez ensuite l'IP attribuée (dans la plage 100.x.y.z) et l'état des pairs :

Fenêtre de terminal
tailscale ip -4
tailscale status

Former le mesh et résoudre les noms avec MagicDNS

Section intitulée « Former le mesh et résoudre les noms avec MagicDNS »

Une fois deux machines connectées, elles se joignent par leur IP Tailscale. Le test décisif est tailscale ping, qui indique si la connexion est directe ou via un relais :

Fenêtre de terminal
tailscale ping node-b
pong from node-b (100.64.0.2) via 172.22.0.4:52433 in 1ms

Le via <ip>:port (et non via DERP) confirme une connexion pair-à-pair directe, ici en 1 ms. C'est exactement ce qu'on attend d'un mesh sain.

MagicDNS rend le réseau lisible : au lieu de retenir des IP, on utilise le nom court de chaque machine. Activé, il résout automatiquement les noms d'appareils :

Fenêtre de terminal
tailscale ip node-b
100.64.0.2
fd7a:115c:a1e0::2

MagicDNS gère aussi des certificats HTTPS valides (via Let's Encrypt) pour vos services internes avec tailscale cert. C'est ce qui permet d'exposer une application en HTTPS sans bricoler d'autorité de certification interne.

Sécuriser le réseau avec les ACL grants (zero-trust)

Section intitulée « Sécuriser le réseau avec les ACL grants (zero-trust) »

Par défaut, un VPN à plat laisse toute machine joindre toutes les autres. Tailscale applique au contraire un modèle zero-trust : la politique d'accès décrit explicitement qui peut joindre quoi, et tout le reste est refusé par défaut. Depuis 2026, le format recommandé est celui des grants, qui contrôlent le réseau et la couche applicative dans une syntaxe plus lisible que les anciennes ACL.

Une politique se définit en HuJSON (du JSON tolérant aux commentaires). Voici une politique de moindre privilège : le groupe ops ne peut joindre les machines taguées prod que sur SSH et HTTPS, rien d'autre.

{
"groups": {
"group:ops": ["alice@example.com", "bob@example.com"],
},
"tagOwners": {
"tag:prod": ["group:ops"],
},
"grants": [
{
"src": ["group:ops"],
"dst": ["tag:prod"],
"ip": ["tcp:22", "tcp:443"],
},
],
}

L'effet est vérifiable. Dans le lab, après avoir tagué une machine en prod, une connexion vers le port autorisé aboutit, une connexion vers un port hors politique reste silencieusement bloquée :

Fenêtre de terminal
# Port 443 (autorisé par le grant)
nc -zv node-b 443 # node-b (100.64.0.2:443) open
# Port 8000 (hors grant)
nc -zv node-b 8000 # node-b (100.64.0.2:8000): Operation timed out

Versionnez ce fichier dans Git : la politique devient revue en pull request et testable en CI, exactement comme du code d'infrastructure.

Étendre le réseau : subnet routers et exit nodes

Section intitulée « Étendre le réseau : subnet routers et exit nodes »

Un subnet router expose un réseau local entier (une baie, un VLAN, un cloud) au tailnet, sans installer Tailscale sur chaque machine de ce réseau :

Fenêtre de terminal
sudo tailscale up --advertise-routes=192.168.1.0/24

La route annoncée doit ensuite être approuvée côté administration. Un exit node va plus loin : il route tout le trafic Internet d'un client à travers lui, utile pour sortir depuis une région précise ou sécuriser un poste sur un réseau hostile. Il faut d'abord activer le routage IP sur le nœud de sortie :

Fenêtre de terminal
echo 'net.ipv4.ip_forward = 1' | sudo tee /etc/sysctl.d/99-tailscale.conf
echo 'net.ipv6.conf.all.forwarding = 1' | sudo tee -a /etc/sysctl.d/99-tailscale.conf
sudo sysctl -p /etc/sysctl.d/99-tailscale.conf
sudo tailscale up --advertise-exit-node

Côté client, on choisit le nœud de sortie une fois approuvé :

Fenêtre de terminal
sudo tailscale up --exit-node=node-b

L'oubli du routage IP est la cause numéro un d'un exit node qui « se connecte mais ne donne pas Internet ». Vérifiez toujours sysctl net.ipv4.ip_forward (doit renvoyer 1).

Tailscale SSH supprime la gestion des clés SSH : l'authentification s'appuie sur l'identité du tailnet et la politique. On l'active sur la machine cible, puis une règle ssh dans la politique décide qui peut se connecter et sous quelle identité :

Fenêtre de terminal
sudo tailscale up --ssh

Serve et Funnel exposent un service local. serve le rend accessible au sein du tailnet uniquement, avec HTTPS automatique ; funnel le publie sur l'Internet public :

Fenêtre de terminal
tailscale serve 3000 # service privé, tailnet seulement
tailscale funnel 3000 # service exposé publiquement, HTTPS automatique

funnel est pratique pour un webhook ou une démo, mais c'est une exposition publique : réservez-la à des services conçus pour l'être, et préférez serve par défaut.

Reprendre le contrôle : Headscale, le coordination server souverain

Section intitulée « Reprendre le contrôle : Headscale, le coordination server souverain »

C'est ici que se joue la souveraineté. Headscale est une implémentation open source (BSD-3) du coordination server : vous gardez les clients Tailscale officiels, mais vous pointez leur plan de contrôle vers votre propre serveur. Plus aucune dépendance à Tailscale Inc, et toutes les métadonnées restent sur votre infrastructure (région européenne, conformité RGPD). La version stable à la rédaction est v0.29.1.

On le déploie en conteneur. Le docker-compose.yml minimal :

services:
headscale:
image: headscale/headscale:0.29.1
command: serve
restart: unless-stopped
volumes:
- ./config:/etc/headscale
- ./lib:/var/lib/headscale

Partez du fichier de configuration officiel de la version (le config-example.yaml du dépôt), et ne touchez qu'à l'essentiel : server_url (l'URL publique de votre Headscale), listen_addr, et base_domain pour MagicDNS (ce domaine doit différer de celui de server_url). On crée ensuite un utilisateur et une clé de pré-authentification :

  1. Créer l'utilisateur :

    Fenêtre de terminal
    docker compose exec headscale headscale users create equipe
  2. Générer une clé de pré-authentification réutilisable (l'identifiant de l'utilisateur s'obtient avec headscale users list) :

    Fenêtre de terminal
    docker compose exec headscale headscale preauthkeys create --user 1 --expiration 1h --reusable
  3. Enregistrer un client en le pointant vers votre serveur :

    Fenêtre de terminal
    sudo tailscale up --login-server=https://headscale.example.com --authkey <clé>

Le serveur voit alors ses nœuds, en ligne et joignables :

Fenêtre de terminal
docker compose exec headscale headscale nodes list
ID | Name | User | IP address | Online
1 | node-a | equipe | 100.64.0.1 | online
2 | node-b | equipe | 100.64.0.2 | online

Headscale gère les grants, MagicDNS, les subnet routers (avec sonde de santé), les exit nodes, Tailscale SSH et les tags. La politique se gère en mode fichier (déclaratif, versionnable) en renseignant policy.path dans la configuration, puis en rechargeant le service. Deux limites à connaître : pas de console web officielle (des interfaces communautaires existent) et un projet encore en 0.x dont l'API n'est pas figée.

Là où Headscale réutilise les clients Tailscale, NetBird est une plateforme mesh complète et indépendante : ses propres agents, son control plane, son interface web de gestion, son infrastructure de relais. Tout est open source et auto-hébergeable de bout en bout (version v0.73 à la rédaction). NetBird permet aussi de choisir son propre plan d'adressage (un network_range configurable, y compris hors du CGNAT), là où Headscale reste contraint au 100.64.0.0/10.

Le bon choix selon le besoin : Tailscale managé pour la simplicité, Headscale pour garder l'écosystème Tailscale avec un plan de contrôle souverain, NetBird pour une plateforme d'accès interne complète avec UI, SSO et plan d'adressage libre.

La force de Tailscale est aussi sa surface de confiance : vous faites confiance au coordination server pour distribuer les bonnes clés publiques. Quelques réflexes réduisent le risque :

  • Grants minimalistes versionnés en Git, segmentés par group: et tag:, n'ouvrant que les ports nécessaires plutôt que *.
  • Expiration des clés activée (rotation forcée), avec device approval pour qu'aucun appareil ne rejoigne le réseau sans validation.
  • Tailnet lock pour signer les nœuds et empêcher un control plane compromis d'injecter une machine pirate.
  • Pour les besoins de souveraineté, control plane Headscale ou NetBird auto-hébergé en région européenne.
SymptômeCause probableSolution
tailscale status affiche relay au lieu de directNAT symétrique ou CGNATtailscale netcheck pour diagnostiquer ; la connexion reste fonctionnelle via DERP
Résolution DNS qui casse quand Tailscale monteconflit MagicDNS avec systemd-resolvedpasser à un client récent (v1.98+), vérifier /etc/resolv.conf
Exit node sans accès Internetroutage IP désactivésysctl net.ipv4.ip_forward doit valoir 1
« ça ping mais le service ne répond pas »trafic bloqué par un grantrevoir la politique : ping n'est pas filtré, le TCP oui
HTTPS lent sur lien PPPoE/tunnelMTU trop élevé pour l'encapsulation WireGuardréduire le MTU de l'interface en aval
  • Tailscale crée un mesh WireGuard chiffré de bout en bout, sans ouvrir de port, avec traversée de NAT automatique (relais DERP en repli).
  • MagicDNS résout les machines par leur nom ; tailscale ping distingue direct et relay.
  • Les grants (format 2026) imposent un accès zero-trust deny-by-default, vérifiable port par port et versionnable en Git.
  • Subnet routers et exit nodes étendent le réseau ; les exit nodes exigent le routage IP (ip_forward=1).
  • Headscale auto-héberge le coordination server pour la souveraineté ; NetBird est une alternative complète avec UI.
  • Pièges récurrents : self-access des appareils d'un même utilisateur, et propagation de la netmap après un changement de politique.

Ce site vous est utile ?

Sachez que moins de 1% des lecteurs soutiennent ce site.

Je maintiens +700 guides gratuits, sans pub ni tracking. Un soutien, même symbolique, m'aide à couvrir l'hébergement et à garder ces ressources gratuites. Merci pour votre appui.

Le formulaire ne s'affiche pas ? Ouvrir Ko-fi dans un onglet.

Abonnez-vous et suivez mon actualité DevSecOps sur LinkedIn