Pourquoi auto-héberger le serveur de contrôle
Tailscale est un logiciel exceptionnel — les performances de WireGuard, des clients multiplateformes, un magic DNS qui fonctionne du premier coup. Il ne transporte pourtant pas votre trafic : les paquets WireGuard circulent directement d’un nœud à l’autre, chiffrés de bout en bout. Ce qui tourne dans le cloud de Tailscale, c’est le plan de contrôle — le serveur de coordination qui échange les clés publiques, fait se découvrir les pairs, attribue les adresses 100.x.y.z, publie les noms MagicDNS, distribue les ACL et la carte des relais DERP. Sans lui, les nœuds ne se trouvent pas. Headscale réimplémente ce serveur de coordination en Go open source, avec le même protocole, pour que vous puissiez le faire tourner sur votre propre infrastructure : vos clients Tailscale ne changent que d’URL de connexion.
Au-delà du plafond de 6 utilisateurs de l’offre gratuite, la principale raison de l’auto-héberger est la résidence des données : le plan de contrôle sait quelles machines sont sur votre réseau, leurs IP, leurs noms et vos politiques d’ACL. Faire tourner Headscale signifie que ces informations restent sur votre VPS — et non dans le cloud de quelqu’un d’autre — et que votre réseau ne dépend plus d’une décision tarifaire ou d’une panne chez un tiers.
Ce que Headscale auto-hébergé vous apporte
- Utilisateurs et appareils sans plafond : aucune limite imposé par le logiciel, pas de tarification par siège — votre réseau maillé grandit avec votre équipe, borné seulement par les ressources du VPS.
- Les clients Tailscale officiels partout : Linux, macOS, Windows, iOS, Android — aucun fork maison ni client alternatif requis.
- Tunnels WireGuard pair à pair : Headscale ne gère que l’échange de clés et la distribution des ACL ; tout le trafic réel circule directement en WireGuard entre les pairs.
- Magic DNS : résolvez chaque nœud de votre réseau maillé par son nom d’hôte (
poste.mesh.votre-domaine.com) sans modifier votre DNS public. - Politiques HuJSON : le même format de fichier de politique que Tailscale, avec les ACL et les Grants — définissez quels utilisateurs et quels appareils peuvent accéder à quels services.
- Prise en charge des relais DERP : utilisez la carte DERP publique de Tailscale pour la traversée de NAT, ou activez le serveur DERP intégré à Headscale pour garder aussi les relais chez vous.
- API et CLI headscale : créez des utilisateurs, émettez des clés de pré-authentification et gérez les nœuds par programmation.
- Enregistrement sans compte tailscale.com : vos collaborateurs rejoignent le réseau avec une clé que vous générez, ou via le fournisseur OpenID Connect que vous utilisez déjà (Keycloak, Authelia, Google Workspace…).
- Sous-réseaux, exit nodes, Taildrop et Tailscale SSH : les fonctions de base de Tailscale sont prises en charge par le serveur.
- Moins de 50 Mo de RAM au repos — tourne confortablement sur un VPS de 1 Go aux côtés d’autres charges de travail.
Prérequis
Un VPS avec au moins 1 vCPU et 512 Mo de RAM (1 Go laisse de la marge au système) sous Ubuntu 22.04 ou plus récent, ou Debian 12 ou plus récent — ce sont les distributions que visent les paquets officiels. Pour la voie Docker, installez Docker et Docker Compose v2. Une IP publique est nécessaire ; une configuration double pile IPv4 + IPv6 est recommandée.
Un nom de domaine pointant vers le VPS est requis : les clients Tailscale se connectent au serveur de contrôle par URL, et le projet recommande fortement de servir Headscale en HTTPS sur le port 443, le client Tailscale supposant ce port dans certaines situations. Prévoyez un sous-domaine pour le serveur (vpn.votre-domaine.com) et un autre, distinct, pour les noms MagicDNS (mesh.votre-domaine.com) : Headscale refuse que le domaine MagicDNS soit celui de server_url.
Les ports à ouvrir sur le VPS
- TCP 443 : obligatoire. C’est par HTTPS que les clients s’enregistrent et reçoivent la carte du réseau ; c’est le seul port indispensable au serveur de coordination.
- TCP 80 : uniquement pour la validation Let’s Encrypt HTTP-01 et la redirection vers HTTPS.
- UDP 3478 : uniquement si vous activez le serveur DERP intégré, qui en a besoin pour STUN. Avec la carte DERP publique de Tailscale, inutile de l’ouvrir.
- TCP 8080 (Headscale), 9090 (métriques) et 50443 (gRPC) : à laisser sur
127.0.0.1. Le reverse proxy parle à 8080 en local ; n’exposez jamais l’interface d’administration gRPC sans en avoir besoin.
Déployer Headscale avec Docker Compose
Rédiger le fichier Compose
La stack ne compte qu’un seul service : l’image officielle
docker.io/headscale/headscale:0.29.7, dernière version stable au 7 novembre 2026, lancée avec la commandeserve. Headscale garde tout — clés, base de données, enregistrements des nœuds — dans deux dossiers de l’hôte : la configuration, montée en lecture seule sur/etc/headscale, et les données, sur/var/lib/headscale. Aucun service externe de base de données ou de cache n’est nécessaire ; SQLite est intégré.mkdir -p /opt/headscale/config /opt/headscale/lib cat > /opt/headscale/compose.yaml <<'EOF' services: headscale: image: docker.io/headscale/headscale:0.29.7 container_name: headscale restart: unless-stopped read_only: true tmpfs: - /var/run/headscale ports: - "127.0.0.1:8080:8080" volumes: - /opt/headscale/config:/etc/headscale:ro - /opt/headscale/lib:/var/lib/headscale command: serve healthcheck: test: ["CMD", "headscale", "health"] EOFLe port n’est publié que sur
127.0.0.1: c’est le reverse proxy qui fera face à Internet.Générer la configuration
Partez du fichier d’exemple de la version exacte que vous déployez, puis ne modifiez que les clés ci-dessous.
server_urlest l’URL HTTPS à laquelle vos clients Tailscale se connecteront ;listen_addrdoit écouter sur0.0.0.0à l’intérieur du conteneur. La base se déclare sousdatabase(type: sqlite). Seule la clé noise reste à indiquer : Headscale la génère au premier démarrage si le fichier n’existe pas.curl -Lo /opt/headscale/config/config.yaml \ https://raw.githubusercontent.com/juanfont/headscale/v0.29.7/config-example.yamlserver_url: https://vpn.votre-domaine.com listen_addr: 0.0.0.0:8080 noise: private_key_path: /var/lib/headscale/noise_private.key database: type: sqlite sqlite: path: /var/lib/headscale/db.sqlite dns: magic_dns: true base_domain: mesh.votre-domaine.comSur ServOrbit, le job de provisioning écrit
config.yamlà partir de votre domaine automatiquement.Démarrer le conteneur et vérifier
Lancez
docker compose up -ddepuis/opt/headscale. Headscale génère sa clé noise, puis se met à écouter sur le port 8080. Vérifiez l’endpoint de santé :curl -s http://127.0.0.1:8080/healthdoit renvoyer{"status":"pass"}. L’image est construite sur une base distroless, sans shell, mais la CLI headscale s’appelle directement :docker exec headscale headscale versionconfirme la version en cours d’exécution, etdocker logs --follow headscaleaffiche les journaux.Créer un utilisateur et générer une clé d’authentification
Headscale organise les appareils en « utilisateurs » (auparavant appelés namespaces). Créez-en un, puis relevez son identifiant numérique : l’option
--userdepreauthkeys createattend cet ID, pas le nom.docker exec headscale headscale users create myteam docker exec headscale headscale users list docker exec headscale headscale preauthkeys create --user 1 --reusable --expiration 24hSans
--reusable, la clé ne sert qu’une fois, et sans--expirationelle expire au bout d’une heure. Copiez la clé affichée — vous l’utiliserez pour enregistrer chaque appareil.Enregistrer un appareil
Sur n’importe quel appareil disposant du client Tailscale officiel, pointez-le vers votre instance Headscale :
tailscale up --login-server https://vpn.votre-domaine.com --authkey <votre-clé-preauth>. L’appareil apparaît dans la liste des nœuds de Headscale en quelques secondes :docker exec headscale headscale nodes list. Sans clé, le client ouvre une page qui affiche un identifiant d’authentification ; approuvez-le côté serveur avecdocker exec headscale headscale auth register --user myteam --auth-id <AUTH_ID>. Une fois enregistrés, les appareils communiquent par leur IP de maillage (100.64.x.y) ou par leur nom d’hôte magic DNS.Définir les politiques de contrôle d’accès
Tant qu’aucune politique n’est chargée, Headscale laisse passer tout le trafic entre les nœuds — acceptable pour une petite équipe de confiance, pas dès que postes de développeurs, prestataires et serveurs de production cohabitent. Créez
/opt/headscale/config/policy.hujsonet déclarez-le dansconfig.yamlsouspolicy.path: /etc/headscale/policy.hujson. Headscale utilise le même format HuJSON que Tailscale : définissez des groupes d’utilisateurs, étiquetez les appareils comme serveurs ou postes de travail, et écrivez des règles précisant quels groupes peuvent atteindre quels ports. Le projet recommande les Grants plutôt que les ACL, que Tailscale considère comme historiques. Validez le fichier avecheadscale policy check --file <fichier>, puis rechargez : la politique n’est relue qu’au rechargement (docker kill --signal=HUP headscale, ousystemctl reload headscaleen natif). Versionnez ce fichier dans un dépôt Git privé pour garder l’historique des accès.Se connecter la première fois
Headscale n’a pas d’interface web ni de compte : c’est le serveur de contrôle de votre réseau privé, piloté en ligne de commande. En SSH :
docker exec -it headscale headscale users create monuser, puis la clé avecpreauthkeys create --user <ID> --reusable --expiration 24h, et raccordez vos machines avectailscale up --login-server https://<votre-domaine> --authkey <clé>. Les applications graphiques savent aussi viser un serveur personnalisé : sur macOS, Option + clic sur l’icône Tailscale, menu Debug, puisCustom Login Server; sur iOS,Use custom coordination serverdans le menu de connexion ; sur Android,Use an alternate server; sur Windows,tailscale login --login-serverdans PowerShell.
Sans Docker : le paquet .deb officiel
Le projet publie des paquets .deb pour Debian et Ubuntu, et les recommande sur ces distributions : ils créent l’utilisateur système headscale, posent une configuration par défaut et installent l’unité systemd. C’est la voie la plus simple si Headscale est seul sur son VPS ou si vous préférez un service natif à un conteneur. Les commandes de gestion sont les mêmes que plus haut, sans le préfixe docker exec headscale.
Installer Headscale avec le paquet .deb
Télécharger et installer le paquet
Récupérez le paquet de la version voulue sur la page des releases GitHub, pour votre architecture (
amd64ouarm64), et installez-le avecapt, qui résout les dépendances :HEADSCALE_VERSION="0.29.7" HEADSCALE_ARCH="amd64" wget --output-document=headscale.deb \ "https://github.com/juanfont/headscale/releases/download/v${HEADSCALE_VERSION}/headscale_${HEADSCALE_VERSION}_linux_${HEADSCALE_ARCH}.deb" sudo apt install ./headscale.debVérifiez ensuite :
headscale versiondoit afficher0.29.7.Adapter la configuration
Le paquet dépose la configuration dans
/etc/headscale/config.yamlet un exemple à jour dans/usr/share/doc/headscale/examples/config-example.yaml. Modifiez les mêmes clés que pour Docker (server_url,database,dns.base_domain), mais gardezlisten_addr: 127.0.0.1:8080: le service tourne sur l’hôte, seul le reverse proxy doit l’atteindre. Les données vivent dans/var/lib/headscale.Redémarrer et vérifier le service
Relancez le service pour qu’il relise la configuration, puis contrôlez son état et ses journaux :
sudo systemctl restart headscale sudo systemctl status headscale sudo journalctl -u headscale -fLa sortie doit afficher
Active: active (running). Au premier démarrage, les erreurs les plus fréquentes sont uneserver_urlsans schémahttps://et un domaine MagicDNS identique à celui du serveur.Utiliser la CLI sans être root
La CLI parle au service par le socket Unix
/var/run/headscale/headscale.sock, accessible par défaut au seul utilisateurheadscaleet à root. Passez parsudo, exécutez les commandes en tant qu’utilisateurheadscale, ou ajoutez votre compte au groupeheadscale. Vérifiez l’accès avecheadscale users list.
Exposer Headscale en HTTPS derrière Nginx
Les clients Tailscale parlent à Headscale avec leur propre protocole, transporté par une connexion WebSocket ouverte en POST, dont l’en-tête Upgrade vaut tailscale-control-protocol. Le reverse proxy doit donc relayer les en-têtes Upgrade et Connection tels quels et ne pas mettre les réponses en tampon. La configuration ci-dessous suit l’exemple de la documentation du projet ; obtenez d’abord le certificat avec certbot (certbot certonly --nginx -d vpn.votre-domaine.com).
map $http_upgrade $connection_upgrade {
default keep-alive;
'' close;
}
server {
listen 80;
server_name vpn.votre-domaine.com;
location = /generate_204 { return 204; }
location / { return 301 https://$server_name$request_uri; }
}
server {
listen 443 ssl;
http2 on;
server_name vpn.votre-domaine.com;
ssl_certificate /etc/letsencrypt/live/vpn.votre-domaine.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/vpn.votre-domaine.com/privkey.pem;
location / {
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_buffering off;
proxy_pass http://127.0.0.1:8080;
}
}Laissez tls_cert_path et tls_key_path vides dans config.yaml : Nginx termine TLS, et l’avertissement listening without TLS au démarrage est alors attendu. Pour que les journaux de Headscale montrent la vraie IP des clients, déclarez le proxy comme source de confiance. En installation native, la boucle locale suffit ; en Docker, ajoutez aussi le réseau du pont Docker (docker network inspect), car le conteneur voit arriver le proxy depuis sa passerelle :
trusted_proxies:
- 127.0.0.1/32
- ::1/128Caddy fonctionne aussi, avec un simple reverse_proxy 127.0.0.1:8080. En revanche, le proxy de Cloudflare et Cloudflare Tunnel ne sont pas pris en charge, faute de gérer les WebSocket en POST : l’enregistrement DNS du serveur doit rester en mode DNS seulement.
Associer Headscale à Authelia pour un accès réseau protégé par MFA
Headscale gère le contrôle d’accès au niveau réseau (quelles IP peuvent communiquer avec quels pairs), mais il n’applique pas d’authentification au niveau applicatif. Associez-le à Authelia sur le même VPS : faites passer vos services internes (accessibles uniquement via le maillage) par la couche forward-auth d’Authelia. Les utilisateurs doivent valider un MFA pour accéder aux services, même une fois sur le maillage — de la défense en profondeur pour les charges sensibles.
Vérifier que les nœuds se voient
Un nœud listé par headscale nodes list prouve seulement qu’il a joint le serveur de coordination. Avant d’y faire passer du trafic réel, vérifiez le chemin de bout en bout depuis les clients eux-mêmes.
Contrôler le maillage depuis un client
Lire l’état du réseau
Sur un nœud enregistré, lancez
tailscale status. La commande liste les pairs avec leur IP de maillage, leur nom et le type de liaison :directsignifie que le tunnel WireGuard est établi sans intermédiaire,relayqu’il passe par un relais DERP — fonctionnel, mais avec une latence plus élevée.Tester la connectivité
Relevez l’IP d’un pair dans
tailscale status, puisping 100.64.0.2, ou mieuxtailscale ping <nom-du-nœud>, qui indique si la réponse arrive en direct ou par un relais. Si le pair est listé mais ne répond pas,tailscale netcheckmesure la latence vers chaque région DERP et signale un UDP sortant filtré.Tester la résolution MagicDNS
Avec
dns.magic_dns: true, chaque nœud répond à son nom court et à son nom complet :ping nom-du-noeudouping nom-du-noeud.mesh.votre-domaine.com. Aucun enregistrement DNS public n’est nécessaire pour ces noms internes. Si la résolution échoue,tailscale dns statusmontre la configuration DNS que le client a reçue du serveur.
Cas d’usage : SSH entre VPS sans exposer le port 22
Une fois vos VPS de développement et de production enregistrés sur le même maillage, SSH n’a plus besoin d’écouter sur l’interface publique : la connexion passe par l’adresse de maillage, dans le tunnel WireGuard. Gardez un accès console ouvert chez votre fournisseur avant de toucher au pare-feu, au cas où une règle vous enfermerait dehors.
Restreindre SSH au réseau maillé
Repérer l’interface et l’adresse de maillage
Sur chaque VPS, le client Tailscale crée l’interface
tailscale0.tailscale ip -4affiche l’adresse de maillage de la machine ; notez celle du serveur de production, par exemple100.64.0.3.N’autoriser SSH que par tailscale0
Sur le VPS de production, autorisez SSH sur l’interface de maillage avant de le refuser ailleurs — UFW applique la première règle qui correspond :
ufw allow in on tailscale0 to any port 22 proto tcp ufw deny 22/tcp ufw enable ufw status verboseLe port 22 n’est plus joignable depuis Internet, mais reste ouvert à tous les nœuds du maillage autorisés par votre politique.
Se connecter par le maillage
Depuis le VPS de développement ou votre poste enregistré, connectez-vous par l’IP ou le nom MagicDNS :
ssh [email protected]oussh [email protected]. Tout transite dans le tunnel chiffré ; restreignez ensuite dans la politique Headscale quels groupes peuvent atteindre le port 22 de ce serveur.
Migrer une équipe déjà sur Tailscale
La bascule ne demande aucune réinstallation : le client officiel accepte l’option --login-server, et il suffit de le déconnecter de l’ancien réseau puis de le reconnecter à votre serveur. Chaque machine est coupée quelques secondes pendant l’opération. Comme votre serveur attribue lui-même les adresses 100.x et les noms MagicDNS, prévoyez de mettre à jour les scripts, règles de pare-feu et fichiers de configuration qui citent les anciennes.
Basculer les clients vers Headscale
Reprendre la politique d’accès
Copiez le fichier de politique de votre tailnet depuis l’éditeur Access controls de la console Tailscale et repartez-en : le format HuJSON est le même. Contrôlez-le avec
headscale policy checkavant la bascule — certaines fonctions, comme les IP sets, ne sont pas prises en charge, et seul un sous-ensemble des autogroups existe.Déconnecter puis reconnecter chaque client
Sur Linux ou en ligne de commande, enchaînez la déconnexion et la reconnexion avec une clé de pré-authentification :
tailscale logout tailscale up --login-server https://vpn.votre-domaine.com --authkey <PREAUTHKEY>Sur macOS, iOS, Android et Windows, déconnectez le compte dans l’application, puis ajoutez le serveur personnalisé comme décrit plus haut.
Commencer par les machines de test
Basculez d’abord un poste et un serveur de test, vérifiez
tailscale statuset la résolution des noms, puis les machines de développement et, en dernier, la production. Tant que des machines restent de l’autre côté, les deux réseaux ne se voient pas : planifiez la bascule d’un service et de ses clients dans la même fenêtre.Fermer ou rétrograder le compte Tailscale
Une fois tous les nœuds migrés et vérifiés, fermez le tailnet ou repassez sur le plan Personal si vous gardez un usage personnel. Générez une nouvelle clé de pré-authentification pour les prochains enregistrements plutôt que de garder indéfiniment celle de la migration.
Headscale vs Tailscale Cloud
Tailscale Cloud reste la voie la plus simple pour les petites équipes : aucun serveur à maintenir, des mises à jour automatiques, une facturation intégrée — et le plan Personal reste gratuit jusqu’à 6 utilisateurs. Au-delà, le plan Standard est affiché à 8 $ par utilisateur et par mois sur la grille entrée en vigueur le 8 avril 2026 : 40 $ par mois pour cinq personnes, 80 $ pour dix. Headscale coûte le prix du VPS, quel que soit l’effectif — sur ServOrbit, le VPS Solo (2 vCPU / 4 GB) à 99 DH/mois suffit pour des dizaines de nœuds — plus le temps d’exploitation : mises à jour, sauvegardes, surveillance.
Headscale prend donc tout son sens au-delà de 6 utilisateurs, lorsque la résidence des données impose de garder la topologie de votre réseau hors des serveurs tiers, ou lorsque vous voulez une auditabilité complète de vos logs de coordination. Les clients sont identiques — passer de Tailscale Cloud à Headscale revient à relancer tailscale up --login-server sur chaque appareil.
Tailscale Personal, Tailscale Standard et Headscale
Faites défiler le tableau
| Critère | Tailscale Personal | Tailscale Standard | Headscale sur VPS |
|---|---|---|---|
| Prix | Gratuit | 8 $ par utilisateur et par mois | Le coût du VPS |
| Utilisateurs | Jusqu’à 6 | Sans plafond | Aucune limite logicielle |
| Appareils des utilisateurs | Sans plafond | Sans plafond | Aucune limite logicielle |
| Serveur de coordination | Cloud Tailscale | Cloud Tailscale | Votre VPS, sous votre contrôle |
| Client | Client Tailscale officiel | Client Tailscale officiel | Client Tailscale officiel (`--login-server`) |
| MagicDNS, ACL, Grants | Oui | Oui | Oui, sur votre propre domaine |
| Funnel et Serve | Proposés par Tailscale | Proposés par Tailscale | Non pris en charge |
| Maintenance | Nulle (service managé) | Nulle (service managé) | Mises à jour, sauvegardes, surveillance |
Ce que Headscale ne remplace pas
Headscale implémente le serveur de coordination, pas toute la plateforme commerciale. Sa propre documentation liste ce qui manque : Tailscale Funnel (publier un service local sur Internet), Tailscale Serve (le partager dans le tailnet par un proxy local) et les journaux de flux réseau ne sont pas pris en charge ; les groupes OIDC ne peuvent pas servir dans les ACL. Il n’y a pas non plus de console web officielle : tout passe par la CLI ou l’API. Si votre usage se limite à la connectivité maillée, à MagicDNS, aux politiques d’accès, aux sous-réseaux et aux exit nodes, Headscale couvre le besoin ; si vous utilisez activement Funnel ou Serve, pesez ces fonctions face à l’économie avant de migrer — ou regardez NetBird, qui auto-héberge aussi son interface d’administration.
Sauvegardes et mises à jour
- Sauvegardez avant chaque mise à jour : Headscale applique ses migrations de base au démarrage de la nouvelle version. Arrêtez le service, copiez la configuration et les données, puis relancez :
cd /opt/headscale && docker compose stop,cp -aR /opt/headscale /opt/headscale.backup-$(date +%Y%m%d%H%M%S),docker compose up -d. - Le dossier de données contient
db.sqliteetnoise_private.key: perdre cette clé change l’identité du serveur et oblige tous les nœuds à se réenregistrer. Copiez-le aussi hors du VPS, par exemple vers un stockage objet. - Montez de version en version : le projet impose de passer par chaque version mineure stable (par exemple
0.27puis0.28puis0.29), toujours dans son dernier correctif, sans en sauter. - Lisez la note de version sur GitHub avant chaque mise à jour : elle signale les changements cassants de configuration. Comparez ensuite votre
config.yamlau fichier d’exemple de la nouvelle version.
Dépannage : les erreurs les plus fréquentes
- Le client n’arrive pas à s’enregistrer : vérifiez que
https://vpn.votre-domaine.com/healthrépond depuis l’extérieur, queserver_urlcorrespond exactement au domaine du certificat, et que le proxy relaie bien les en-têtesUpgradeetConnection. - Connexions en
relayau lieu dedirect: un NAT strict ou un pare-feu bloque l’UDP entre les deux nœuds. Lanceztailscale netcheckdes deux côtés pour voir ce qui est filtré ; le trafic passe quand même, par les relais DERP. - Nœud listé
offline: le client a joint le serveur mais ne maintient pas la session. Vérifiezsystemctl status tailscaledetjournalctl -u tailscaledsur le nœud ; un pare-feu local qui coupe les connexions sortantes est la cause la plus courante. - Headscale refuse de démarrer après une mise à jour : une clé de configuration a été renommée ou retirée. Le journal la nomme ; reportez-vous au fichier d’exemple de la version installée.
- MagicDNS ne résout pas : contrôlez que
dns.magic_dnsest àtrueet quedns.base_domaindiffère du domaine deserver_url, puis reconnectez le client pour qu’il récupère la nouvelle configuration DNS.