SSD Nodes Learn 8GB de RAM — $66/an
Guides Matt ConnorPar Matt Connor · Mis à jour le 2026-08-02

Headscale : héberger votre propre serveur Tailscale

Déployez headscale sur un VPS avec le paquet .deb officiel, définissez server_url avant le démarrage, puis connectez votre premier nœud Tailscale.

Verified Every command ran end-to-end on a fresh Ubuntu 24.04 server, July 30, 2026.

Ce qu’est headscale

Headscale est une implémentation auto-hébergée du serveur de contrôle Tailscale. La machine qui coordonne votre réseau privé est donc un VPS dont vous êtes propriétaire. Il s’agit d’un projet communautaire, qui n’est pas géré par Tailscale Inc. Chaque machine exécute toujours le client officiel tailscale, configuré pour utiliser votre serveur avec un seul flag : --login-server.

Le serveur de contrôle sait quelles machines appartiennent au réseau. Il attribue à chaque nœud une adresse de la plage 100.64.0.0/10, distribue les clés publiques et indique aux nœuds comment se trouver entre eux. Les tunnels restent basés sur WireGuard et sont établis directement entre les nœuds. Le trafic entre deux de vos machines ne passe pas par le serveur headscale, sauf si aucun chemin direct ne peut être établi et que les nœuds basculent vers un relay.

Chaque instance de headscale dessert un seul tailnet (un seul réseau Tailscale). Le projet le présente comme adapté à un usage personnel ou à une petite organisation. Avec trois ou quatre machines, un VPN WireGuard classique sur un VPS dont vous êtes propriétaire implique moins de logiciels à administrer et moins de risques de panne. Headscale devient utile lorsque vous ne voulez plus écrire manuellement un bloc [Peer] pour chaque nouvel ordinateur portable. Pour une comparaison plus générale des deux modèles, consultez les différences entre WireGuard et Tailscale.

Ce dont vous avez besoin avant l’installation

  • Un VPS exécutant Ubuntu 24.04, avec une adresse IPv4 publique et un accès sudo. Si le serveur est nouveau, suivez d’abord les dix premières minutes sur un nouveau VPS.
  • Un enregistrement DNS A pointant vers cette adresse. Ce guide utilise headscale.example.com.
  • Un second domaine ou sous-domaine pour MagicDNS. Ce guide utilise tailnet.example.net. Il ne doit pas s’agir du même domaine que celui de server_url.
  • Une machine cliente à connecter, sous Linux, macOS, Windows, Android ou iOS.

Installer headscale depuis le fichier .deb officiel

Le projet publie des paquets .deb sur sa page des releases GitHub. En juillet 2026, la version actuelle est 0.29.3. Vérifiez d’abord votre architecture, car le nom du fichier l’indique.

sudo apt update
sudo apt install -y wget
dpkg --print-architecture

Cette commande affiche amd64 sur un VPS x86 standard et arm64 sur une offre de type Ampere ou Graviton. Placez la valeur obtenue dans la variable ci-dessous.

HEADSCALE_VERSION="0.29.3"
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 -y ./headscale.deb
headscale version

Le préfixe ./ devant le nom du fichier est obligatoire. Sans lui, apt recherche dans vos dépôts un paquet nommé headscale.deb et échoue.

Le paquet crée un utilisateur système headscale, écrit un fichier de configuration par défaut /etc/headscale/config.yaml et installe une unité systemd. Il ne démarre pas le service, ce qui est l’ordre correct. La configuration fournie fait écouter server_url sur http://127.0.0.1:8080, qui n’est pas une adresse accessible par vos clients. Un service démarré à ce stade serait donc mal configuré, même s’il démarrait correctement. L’exécution de sudo systemctl is-active headscale à ce stade affiche inactive. C’est attendu, et non une erreur.

Configurez server_url avant de démarrer le service

Modifiez /etc/headscale/config.yaml avec sudo nano /etc/headscale/config.yaml, ou appliquez les trois mêmes changements avec sed. Conservez une copie de l’original, car le fichier est long et contient de nombreux commentaires. C’est votre meilleure référence pour les autres paramètres.

sudo cp /etc/headscale/config.yaml /etc/headscale/config.yaml.orig
sudo sed -i 's|^server_url:.*|server_url: https://headscale.example.com|' /etc/headscale/config.yaml
sudo sed -i 's|^listen_addr:.*|listen_addr: 127.0.0.1:8080|' /etc/headscale/config.yaml
sudo sed -i 's|^  base_domain:.*|  base_domain: tailnet.example.net|' /etc/headscale/config.yaml
sudo grep -E '^(server_url|listen_addr):|^  base_domain:' /etc/headscale/config.yaml

server_url est l’adresse que headscale inscrit dans chaque enregistrement de client. Les clients utilisent ensuite cette chaîne exacte pour se connecter. Elle doit donc être le nom public précédé de https://, et jamais 127.0.0.1.

listen_addr indique sur quelle adresse le processus écoute. Laissez-le sur loopback. Un reverse proxy installé sur le même serveur termine le chiffrement TLS (transport layer security) et lui transmet les requêtes. Aucun accès externe au port 8080 n’est donc nécessaire.

base_domain est le suffixe MagicDNS, c’est-à-dire le domaine sous lequel vos nœuds reçoivent leur nom. Il doit s’agir d’un nom de domaine pleinement qualifié, sans point final. Il doit aussi être différent du domaine indiqué dans server_url, car les deux espaces de noms entreraient sinon en conflit.

Ne modifiez pas la section de la base de données. La valeur par défaut est SQLite dans /var/lib/headscale/db.sqlite, un répertoire créé et géré par le paquet. SQLite suffit pour un tailnet de cette taille.

Démarrer headscale et vérifier qu’il fonctionne

sudo systemctl enable --now headscale
sudo systemctl is-active headscale
curl -sS -o /dev/null -w '%{http_code}\\n' http://127.0.0.1:8080/health

is-active affiche active et curl affiche 200. enable --now effectue les deux opérations : il démarre le service et le configure pour qu’il démarre après un redémarrage.

Si is-active affiche failed, consultez le journal avec sudo journalctl -u headscale -n 50 --no-pager. À ce stade, l’échec vient presque toujours du fichier de configuration, car headscale analyse le fichier entier avant d’ouvrir un socket. Une mauvaise indentation ou une clé inconnue arrête donc le processus avant qu’il n’écoute sur un port. Corrigez le fichier, puis exécutez sudo systemctl restart headscale. Toute modification ultérieure de la configuration nécessite le même redémarrage. Les clients se reconnectent ensuite automatiquement. Si les unités systemd ne vous sont pas familières, exécuter vos propres services et timers avec systemd présente les commandes utilisées ici.

Vérifiez les fichiers d’état depuis le shell :

stat -c '%U %n' /var/lib/headscale/db.sqlite /var/lib/headscale/noise_private.key

Les deux lignes commencent par headscale, l’utilisateur non privilégié créé par le package. noise_private.key est l’identité du serveur auprès de ses clients. Conservez-le. Si vous le supprimez, headscale en génère un nouveau et chaque nœud doit s’enregistrer de nouveau.

Placer TLS devant headscale

Les clients doivent accéder à server_url via HTTPS. Caddy est la solution la plus simple, car il demande et renouvelle lui-même le certificat.

sudo apt install -y caddy

Remplacez /etc/caddy/Caddyfile par le bloc fourni dans la documentation de headscale :

headscale.example.com {
    reverse_proxy 127.0.0.1:8080 {
        header_up True-Client-IP {remote_host}
        header_up X-Real-IP {remote_host}
    }
}
sudo caddy validate --adapter caddyfile --config /etc/caddy/Caddyfile
sudo systemctl restart caddy
sudo systemctl is-active caddy

validate affiche adapted config to JSON lorsque le fichier est correctement analysé. Un avertissement indiquant que le fichier n’est pas formaté est sans conséquence. Depuis votre ordinateur portable, curl -sS -o /dev/null -w '%{http_code}\\n' https://headscale.example.com/health doit également afficher 200. Ce contrôle unique confirme que le DNS, le pare-feu, le certificat et le proxy fonctionnent ensemble.

Voici le détail du proxy qui peut vous faire perdre une soirée. La connexion de contrôle Tailscale est une mise à niveau HTTP. Elle est initiée avec POST, et la valeur de l’en-tête Upgrade est tailscale-control-protocol. Caddy transmet ces éléments sans configuration supplémentaire. nginx ne le fait pas. Un frontal nginx doit donc utiliser la map de mise à niveau :

map $http_upgrade $connection_upgrade {
    default keep-alive;
    ''      close;
}

server {
    listen 443 ssl;
    server_name headscale.example.com;
    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_buffering off;
        proxy_pass http://127.0.0.1:8080;
    }
}

Si vous omettez ces lignes, les requêtes ordinaires continuent de fonctionner. C’est pourquoi /health renvoie 200 et que tout semble correct, alors que la connexion de contrôle persistante ne s’établit jamais. Vos nœuds s’enregistrent, puis restent hors ligne. Si vous choisissez nginx, Certbot sur Ubuntu 24.04 avec nginx couvre la partie certificat.

Ports à ouvrir dans UFW

sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
sudo ufw status verbose

Le port 443 transporte toutes les communications des clients. Le port 80 sert uniquement au challenge HTTP d’ACME (environnement de gestion automatique des certificats) et à la redirection vers HTTPS. Caddy en a également besoin pour obtenir un certificat.

Le port 8080 reste fermé. listen_addr est 127.0.0.1:8080. Le proxy accède donc à headscale via l’interface loopback, sans qu’aucune règle de pare-feu soit nécessaire. Ouvrir le port 8080 sur Internet fournirait aux clients un canal de contrôle en clair, sans aucun avantage. N’oubliez pas que la plupart des fournisseurs exécutent un second pare-feu dans leur panneau de contrôle, distinct d’UFW. Un port peut donc être ouvert sur le serveur tout en restant fermé en périphérie du réseau. Notions de base sur le pare-feu UFW sur un VPS détaille davantage la syntaxe des règles.

Créer un utilisateur et une clé de préauthentification

sudo headscale users create alice
sudo headscale users list

La commande headscale est un client. Elle communique avec le daemon en cours d’exécution via le socket Unix /var/run/headscale/headscale.sock, qui est en mode 0770 et appartient au groupe headscale. Deux conséquences en découlent. La commande échoue lorsque le service est arrêté, ce qui explique aussi pourquoi l’ordre des étapes est important dans ce guide. Elle nécessite également sudo, sauf si vous ajoutez votre propre compte au groupe headscale.

users list affiche un ID à côté de chaque nom. Vous avez besoin de ce numéro, car la commande de création de clé accepte un ID utilisateur numérique, et non un nom.

sudo headscale preauthkeys create --user 1 --expiration 24h

La clé n’est affichée qu’une seule fois. Copiez-la maintenant. Une clé de préauthentification est à usage unique et reste valide pendant une heure, sauf indication contraire. Il est donc utile de définir --expiration 24h pendant vos tests. Ajoutez --reusable pour créer une clé qui inscrit plusieurs machines. Traitez cette clé comme un mot de passe, car toute personne qui la détient peut rejoindre votre réseau.

Connecter votre premier client avec --login-server

Sur la machine que vous voulez joindre :

curl -fsSL https://tailscale.com/install.sh | sh
sudo tailscale up --login-server https://headscale.example.com --auth-key 'hskey-auth-PASTE-YOUR-KEY-HERE'
tailscale status
tailscale ip -4

tailscale ip -4 affiche l’adresse attribuée par headscale, par exemple 100.64.0.1. De retour sur le serveur, sudo headscale nodes list affiche le nœud avec son ID, son utilisateur et son état de connexion.

La valeur de --login-server doit correspondre exactement à server_url, y compris le schéma et sans slash final. Les valeurs sont comparées en tant que chaînes. Une différence signifie que le client s’enregistre auprès d’une adresse, puis reçoit l’instruction de communiquer avec une autre.

Une machine précédemment connectée au service hébergé de Tailscale conserve cette connexion. Exécutez d’abord sudo tailscale logout sur cette machine, puis exécutez tailscale up avec --login-server.

Si vous omettez --auth-key, le client affiche une URL. Ouvrez-la. La page affiche l’identifiant de cette tentative d’enregistrement. Vous pouvez ensuite l’approuver sur le serveur :

sudo headscale auth register --user alice --auth-id PASTE-THE-ID-FROM-THE-PAGE

Ce formulaire convient mieux à votre ordinateur portable personnel. Les clés preauth sont préférables pour les opérations scriptées, car aucune intervention humaine n’est nécessaire.

DERP et ce qui relaie le trafic lorsqu’un chemin direct échoue

DERP (designated encrypted relay for packets) est le chemin de secours. Lorsque deux nœuds ne peuvent pas établir de connexion WireGuard directe, généralement parce qu’ils se trouvent tous les deux derrière un NAT strict (network address translation), ils envoient les paquets via un relais. Le relais ne détient aucune clé. Il ne peut donc pas lire votre trafic. En revanche, il voit quels nœuds communiquent et quelle quantité de données circule.

Il est important de comprendre le fonctionnement de la configuration par défaut. Headscale est fourni avec une configuration qui pointe vers https://controlplane.tailscale.com/derpmap/default, avec auto_update_enabled: true et update_frequency: 3h. Votre control plane vous appartient donc, tandis que vos relais sont ceux de Tailscale. Pour la plupart des utilisateurs, c’est un compromis acceptable. Si ce n’est pas votre cas, exécutez votre propre relais.

Pour exécuter votre propre relais, définissez enabled: true sous derp.server dans config.yaml, redémarrez headscale et ouvrez le port STUN (session traversal utilities for NAT) avec sudo ufw allow 3478/udp. Le fichier de configuration indique clairement cette exigence : server_url doit utiliser https, car DERP nécessite TLS. Vider la liste derp.urls supprime les relais de Tailscale de la carte. Si vous le faites sans disposer d’un relais intégré fonctionnel, toute paire de nœuds qui ne peut pas se connecter directement ne pourra pas se connecter du tout.

Depuis un client, tailscale netcheck affiche la latence vers chaque région de relais qu’il connaît, et tailscale status marque chaque pair comme direct avec une adresse ou relay avec un code de région. Un pair bloqué sur relay indique un problème de NAT, et non un problème de headscale.

Pourquoi un nœud apparaît-il comme hors ligne ?

Le proxy bloque la mise à niveau. C’est le cas le plus fréquent. Sa signature est la suivante : tout le reste semble fonctionner : /health renvoie 200, headscale nodes list affiche le nœud, mais le nœud ne se met jamais en ligne. La connexion de contrôle est un POST qui transporte Upgrade: tailscale-control-protocol. Un proxy qui ne le transmet pas supprime le seul canal qui signale l’état du nœud. Comparez votre configuration nginx avec le bloc map ci-dessus, ou utilisez Caddy pour écarter le proxy comme cause.

server_url a changé après l’enregistrement des nœuds. Les nœuds continuent à utiliser la valeur qui leur a été fournie lors de l’enregistrement. Si vous l’avez modifiée, exécutez sudo tailscale up --login-server https://headscale.example.com --force-reauth sur chaque nœud.

Le client n’est pas en cours d’exécution. Sur le nœud, exécutez sudo systemctl is-active tailscaled et sudo journalctl -u tailscaled -n 50 --no-pager. Un client qui ne peut pas résoudre votre domaine ou y accéder consigne ses nouvelles tentatives à cet endroit.

La clé a expiré. Ce point est traité dans la section suivante.

Pour surveiller le serveur pendant vos tests, exécutez sudo journalctl -u headscale -f sur le VPS et redémarrez tailscaled sur le client. Un nœud qui atteint headscale produit immédiatement des lignes dans les logs. L’absence de sortie signifie que la requête n’arrive pas. Vérifiez donc le DNS, le firewall et le proxy avant d’examiner headscale.

Expiration des clés et nœud qui cesse de fonctionner quelques semaines plus tard

Il existe deux types d’expiration distincts. Les confondre fait perdre du temps.

Les clés Preauth expirent rapidement par conception. La valeur par défaut est d’une heure et d’une utilisation. Si tailscale up refuse la clé, générez-en une nouvelle sur le serveur au lieu de modifier quoi que ce soit sur le client.

Les clés de nœud constituent la partie durable. La section node de config.yaml définit expiry: 0, et 0 signifie qu’il n’existe aucune expiration par défaut : un nœud enregistré reste valide jusqu’à son expiration. Les nœuds associés à un tag n’expirent jamais. Définissez expiry: 180d si vous voulez que les enregistrements expirent automatiquement, et comprenez ce que cela implique : chaque nœud qui n’est pas associé à un tag doit alors utiliser sudo tailscale up --login-server https://headscale.example.com --force-reauth selon cette périodicité, et un serveur headless auquel personne ne s’authentifie de nouveau sera automatiquement retiré du réseau.

Faites-le manuellement lorsqu’une personne perd un ordinateur portable. sudo headscale nodes list fournit l’identifiant, puis sudo headscale nodes expire -i 3 déconnecte ce nœud et sudo headscale nodes delete -i 3 le supprime complètement du réseau.

Sauvegardes et mises à niveau

/var/lib/headscale et /etc/headscale constituent l’ensemble du serveur. Arrêtez le service avant de les copier, car SQLite peut avoir des écritures en cours et une base de données copiée pendant son utilisation peut être incohérente.

sudo systemctl stop headscale
sudo tar czf /root/headscale-state.tgz -C /var/lib headscale
sudo tar czf /root/headscale-config.tgz -C /etc headscale
sudo systemctl start headscale
sudo chmod 600 /root/headscale-*.tgz

Copiez les deux fichiers hors du serveur. Ils contiennent les clés privées et tous les enregistrements. Ils nécessitent donc le même niveau de protection que le serveur lui-même. sauvegardes restic depuis un VPS explique comment planifier ces sauvegardes et les chiffrer.

Les mises à niveau reprennent les étapes de l’installation : téléchargez le nouveau .deb, sudo apt install ./headscale.deb, puis redémarrez le service et exécutez de nouveau les vérifications is-active et /health. Depuis la version 0.29, le chemin de mise à niveau est strict. Le saut d’une version mineure est bloqué, tout comme la rétrogradation vers une version mineure antérieure. Passez à la version mineure suivante une étape à la fois, effectuez une sauvegarde avant chaque étape et lisez d’abord les notes de version correspondantes, car cette version a également modifié le comportement de la stratégie ACL et déplacé plusieurs clés de configuration.

FAQ

Pourquoi headscale ne démarre-t-il pas juste après l’installation du fichier .deb ?

Le paquet installe l’unité, mais laisse le service arrêté. De plus, le fichier /etc/headscale/config.yaml par défaut est un modèle, pas une configuration fonctionnelle. Modifiez d’abord server_url, listen_addr et base_domain, puis exécutez sudo systemctl enable --now headscale et vérifiez avec sudo systemctl is-active headscale. Si le démarrage échoue encore, sudo journalctl -u headscale -n 50 --no-pager indique le problème. À ce stade, il s’agit presque toujours d’une erreur YAML, car headscale analyse tout le fichier avant d’écouter sur un port.

Dois-je quand même installer le client Tailscale standard sur mes machines ?

Oui. Headscale remplace uniquement le control server. Chaque nœud exécute le client officiel de Tailscale, et vous l’orientez vers votre serveur avec sudo tailscale up --login-server https://headscale.example.com. Ce flag existe dans le client standard. Aucun patch ni rebuild n’est donc nécessaire.

Mon trafic passe-t-il par le serveur headscale ?

En général, non. Headscale coordonne le réseau et distribue les clés et les adresses, tandis que le chemin des données passe directement par WireGuard entre vos nœuds. Le trafic ne fait un détour que lorsque deux nœuds ne peuvent pas communiquer directement et utilisent un relais DERP. Avec la configuration fournie, ces relais sont ceux, publics, de Tailscale. Exécutez tailscale status sur un nœud pour vérifier si un peer donné est direct ou utilise un relay.

Pourquoi mon nœud reste-t-il hors ligne après son enregistrement ?

Un nœud qui apparaît dans headscale nodes list, mais ne passe jamais en ligne, a généralement perdu sa connexion de contrôle au niveau du reverse proxy. Cette connexion est une mise à niveau HTTP envoyée avec une requête POST et l’en-tête Upgrade: tailscale-control-protocol. nginx la supprime si vous n’ajoutez pas le bloc map $http_upgrade $connection_upgrade et les lignes proxy_set_header correspondantes. Caddy la transmet sans configuration supplémentaire. C’est donc un moyen rapide de vérifier si le proxy est en cause.

Ai-je besoin d’un nom de domaine et de TLS pour headscale ?

En pratique, oui. Les clients se connectent à la chaîne indiquée dans server_url. Les certificats sont émis pour des noms, et non pour des adresses IP seules. Le fichier de configuration indique également que DERP nécessite TLS. Un domaine associé à Caddy se configure en environ cinq minutes et fournit un endpoint HTTPS qui renouvelle automatiquement son certificat. Si vous exécutez le control server en HTTP simple, chaque échange entre les clients et celui-ci traverse Internet en clair.