Mise en place d'un nœud Headscale

Mise en place d'un nœud Headscale

Mise en place d'un nœud Headscale sur le lab interne ANSSI pour évaluer une alternative auto-hébergée à Tailscale : installation depuis le binaire officiel, configuration de base, service systemd, création d'une preauth-key et enregistrement d'un client Tailscale.
Publié le
Statut
Terminé

Contexte et besoins

Dans le cadre de mon alternance, l’équipe veut évaluer une alternative aux VPN d’accès distant classiques pour relier les machines du lab et les postes des admins en télétravail. Le candidat à étudier est Headscale : un serveur de coordination open source compatible avec les clients Tailscale officiels, qui permet de monter un réseau mesh WireGuard auto-hébergé.

Cette intervention est un POC : je déploie un seul nœud Headscale sur le lab, j’enregistre un client Tailscale depuis mon laptop d’admin, et je vérifie qu’il rejoint bien le réseau. Pas d’ACL fines, pas de reverse proxy HTTPS, pas de relais DERP perso : on reste sur l’essentiel pour valider que la mécanique fonctionne et qu’elle vaut la peine d’être creusée.

Rappel théorique

VPN « hub-and-spoke » vs réseau mesh

Un VPN classique (le WireGuard servi par une passerelle UniFi, par exemple) fait passer tout le trafic par un point central :

Schéma hub-and-spoke : trois clients connectés à un serveur VPN central, qui route ensuite vers le LAN cible

C’est simple, mais le serveur central est un goulot d’étranglement et un point de panne unique.

Un réseau mesh (Tailscale, Headscale, ZeroTier) ne fait que coordonner les pairs. Le trafic réel passe directement de A vers B sans repasser par le serveur :

Schéma mesh : trois clients (100.64.0.1, 100.64.0.2, 100.64.0.3) reliés directement deux à deux par des tunnels WireGuard

Le serveur Headscale distribue les clés publiques et les endpoints (IP/port public de chaque pair). Une fois que A et B connaissent la clé publique l’un de l’autre, ils ouvrent un tunnel WireGuard direct.

Plage d’IP du mesh

Headscale distribue par défaut des IP dans 100.64.0.0/10, plage CGNAT réservée par la RFC 6598. Choix volontaire pour ne pas entrer en conflit avec un LAN interne classique en 10.0.0.0/8 ou 192.168.0.0/16.

Preauth-key

Chaque client doit s’authentifier auprès de Headscale au premier enregistrement. La méthode utilisée pour ce POC est la preauth-key : une chaîne générée côté serveur, qu’on copie sur le client lors de la commande tailscale up. Plus simple qu’OAuth ou OIDC, suffisant pour un POC.

Topologie

ÉquipementRôleAdresse réelleAdresse mesh
VM headscale-srv (Debian 12)Serveur de coordination Headscale192.168.10.230/24
Laptop admin (Linux)Client TailscaleDHCP / 4G100.64.0.1

Prérequis

  • VM Debian 12 dédiée (1 vCPU, 512 Mo RAM, 10 Go disque), IP statique sur le LAN du lab.
  • Une entrée DNS interne : headscale.lab.local192.168.10.230.
  • Compte admin sudo sur la VM pour l’installation.

Installation de Headscale

Récupération du binaire officiel

BASH
HEADSCALE_VERSION="0.23.0"

# Téléchargement du binaire officiel pour amd64
curl -L -o /usr/local/bin/headscale \
    https://github.com/juanfont/headscale/releases/download/v${HEADSCALE_VERSION}/headscale_${HEADSCALE_VERSION}_linux_amd64

chmod +x /usr/local/bin/headscale
headscale version
Cliquez pour développer et voir plus

Explications ligne par ligne :

  • HEADSCALE_VERSION : la version est figée explicitement, plutôt que de tirer un latest. Plus reproductible, et on choisit quand on monte de version.
  • curl -L : suit les redirections (GitHub Releases redirige vers un CDN).
  • chmod +x : rend le binaire exécutable.
  • headscale version : sanity check pour vérifier que le binaire fonctionne.

Préparation des dossiers et de l’utilisateur

BASH
# Utilisateur système dédié, sans shell
useradd -r -s /usr/sbin/nologin -d /var/lib/headscale headscale

# Dossiers de configuration et de données
install -d -o headscale -g headscale /etc/headscale
install -d -o headscale -g headscale /var/lib/headscale

# Récupération de la config par défaut
curl -L -o /etc/headscale/config.yaml \
    https://github.com/juanfont/headscale/raw/v${HEADSCALE_VERSION}/config-example.yaml
chown headscale:headscale /etc/headscale/config.yaml
Cliquez pour développer et voir plus

L’utilisateur système headscale et les dossiers /etc/headscale (config) + /var/lib/headscale (base et clés) suivent le pattern Linux classique. Le service tournera sous cet utilisateur, jamais en root.

Configuration minimale

J’édite /etc/headscale/config.yaml. Pour un POC, seules quelques clés méritent d’être ajustées :

YAML
# URL annoncée aux clients
server_url: http://headscale.lab.local:8080

# Adresse d'écoute du serveur
listen_addr: 0.0.0.0:8080

# Plage d'IP du mesh
prefixes:
  v4: 100.64.0.0/10

# Base de données SQLite (largement suffisant pour un POC)
database:
  type: sqlite
  sqlite:
    path: /var/lib/headscale/db.sqlite

# Clés cryptographiques (générées au premier démarrage)
private_key_path: /var/lib/headscale/private.key
noise:
  private_key_path: /var/lib/headscale/noise_private.key

# Logs
log:
  level: info
Cliquez pour développer et voir plus

Explications :

  • server_url : URL que les clients utiliseront pour parler à Headscale. Elle doit pointer vers la VM via le DNS interne.
  • listen_addr 0.0.0.0:8080 : Headscale écoute en HTTP sur le port 8080. Pour un usage réel hors LAN, on mettra un reverse proxy devant pour terminer TLS.
  • prefixes v4 : range IPv4 distribué aux nœuds du mesh.
  • database type: sqlite : SQLite, suffisant pour quelques dizaines de nœuds. PostgreSQL est l’option pour aller plus loin.
  • private_key_path et noise.private_key_path : clés cryptographiques générées automatiquement au premier démarrage, à sauvegarder.

Toutes les autres clés (DERP, MagicDNS, IPv6, OIDC) restent par défaut pour ce POC.

Service systemd

INI
# /etc/systemd/system/headscale.service
[Unit]
Description=headscale controller
After=network.target

[Service]
Type=simple
User=headscale
Group=headscale
ExecStart=/usr/local/bin/headscale serve
Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target
Cliquez pour développer et voir plus

Explications :

  • User/Group: headscale : exécution sous l’utilisateur dédié, pas en root.
  • Restart=always : relance automatique en cas de crash.
  • Pas de [Service] durci complexe ici : c’est un POC, on garde la conf simple ; on durcira au passage en production (sandboxing systemd, ProtectSystem, etc.).
BASH
# Charger le service et le démarrer
systemctl daemon-reload
systemctl enable --now headscale

# Vérifier l'état
systemctl status headscale
journalctl -u headscale -f
Cliquez pour développer et voir plus

Au premier démarrage, Headscale crée la base SQLite et les clés cryptographiques dans /var/lib/headscale.

Création d’un utilisateur et d’une preauth-key

Headscale isole les nœuds par utilisateur. Je crée un utilisateur lab-admin qui regroupera les devices des admins.

BASH
# Création de l'utilisateur
sudo -u headscale headscale users create lab-admin

# Vérification
sudo -u headscale headscale users list

# Génération d'une clé pré-authentifiée valable 1 heure
sudo -u headscale headscale preauthkeys create \
    --user lab-admin \
    --expiration 1h
Cliquez pour développer et voir plus

La sortie ressemble à :

TEXT
ID  Key                                               Reusable  Expiration
1   abcd1234ef5678...0123456789abcdef0123456789abcd  false     2026-01-26 19:00:00
Cliquez pour développer et voir plus

Je copie la clé. Elle servira à enregistrer le laptop client.

Enregistrement d’un client Tailscale

Sur mon laptop d’admin :

BASH
# Installation du client Tailscale officiel
curl -fsSL https://tailscale.com/install.sh | sh

# Connexion à Headscale au lieu du SaaS Tailscale
sudo tailscale up \
    --login-server=http://headscale.lab.local:8080 \
    --authkey=abcd1234ef5678...0123456789abcdef0123456789abcd

# Vérification
sudo tailscale status
sudo tailscale ip -4
Cliquez pour développer et voir plus

Explications ligne par ligne :

  • Le client Tailscale officiel est compatible avec Headscale parce qu’il sait parler à un autre control plane via --login-server.
  • --authkey : la preauth-key générée à l’étape précédente. Elle permet au client de s’authentifier sans interaction navigateur.
  • tailscale status : montre les nœuds connus du mesh.
  • tailscale ip -4 : affiche l’IP 100.64.x.x attribuée au client.

Vérifications

Côté Headscale

BASH
sudo -u headscale headscale nodes list
Cliquez pour développer et voir plus

Sortie attendue :

TEXT
ID  Hostname        IP             User        Online
1   laptop-admin    100.64.0.1     lab-admin   ✓
Cliquez pour développer et voir plus

Le laptop est bien enregistré et marqué en ligne.

Côté client

BASH
sudo tailscale status
Cliquez pour développer et voir plus

Sortie attendue :

TEXT
100.64.0.1   laptop-admin   lab-admin@   active; direct
Cliquez pour développer et voir plus

Le statut direct indique que le client a établi un tunnel WireGuard. Sur un mesh à un seul nœud, le direct est avec Headscale lui-même ; on prendra plus de sens dès qu’un deuxième pair sera enregistré.

Test de coupure / reprise

BASH
# Coupure du tunnel
sudo tailscale down

# tailscale status → message « Logged out »

# Réouverture
sudo tailscale up
Cliquez pour développer et voir plus

Sans recoller la preauth-key : le client est déjà connu de Headscale, il se reconnecte tout seul.

Problèmes rencontrés et solutions

SymptômeCauseCorrection
tailscale up répond failed to connect to control serverURL mal saisie ou Headscale pas démarréVérifier systemctl status headscale et curl http://headscale.lab.local:8080/health
tailscale up répond invalid pre-auth keyClé expirée ou déjà utiliséeRégénérer une preauth-key avec headscale preauthkeys create
headscale nodes list ne montre pas le nœudLe tailscale up côté client n’a pas réussi à parler au serveurVérifier la connectivité IP entre le laptop et la VM Headscale
Service headscale qui ne démarre pasYAML invalide dans config.yamlLire la sortie de journalctl -u headscale, corriger l’indentation
Base SQLite verrouilléePlusieurs processus Headscale en parallèlesystemctl status headscale, vérifier qu’un seul processus tourne

Compétences du bloc 1 mobilisées

Compétence officielleMobilisation concrète
Installer et configurer des éléments d’infrastructureDéploiement de Headscale (binaire, dossiers, utilisateur système, service systemd, configuration YAML).
Mettre en place et vérifier les niveaux d’habilitation associés à un serviceService exécuté sous un utilisateur dédié, accès au mesh contrôlé par preauth-keys par utilisateur.
Réaliser les tests d’intégration et d’acceptation d’un serviceTest bout en bout : démarrage Headscale, création utilisateur, génération preauth-key, enregistrement client, vérification côté serveur et côté client.
Étudier une solution techniqueComparaison documentée du modèle mesh (Tailscale/Headscale) vs hub-and-spoke (VPN classique) pour orienter le choix de l’équipe.

Bilan

Le POC est concluant : Headscale tourne sur la VM headscale-srv, un client Tailscale s’enregistre en une commande avec une preauth-key, l’IP 100.64.0.1 est attribuée et la communication avec le serveur passe. La mécanique de mesh WireGuard est validée pour un nœud, ce qui permet à l’équipe de décider de poursuivre vers une mise en production ou non.

Trois enseignements :

  • la simplicité d’enregistrement côté client (tailscale up --login-server=... --authkey=...) est l’argument le plus tangible vs un VPN classique : pas de fichier .conf, pas de QR code, pas de génération de clés à la main ;
  • le modèle mesh ne prend tout son sens qu’avec plusieurs pairs : à un seul client, on ne mesure pas encore la différence avec un VPN hub-and-spoke. La suite du POC consistera à ajouter d’autres clients pour valider le P2P ;
  • la réutilisation des clients Tailscale officiels est un atout : pas besoin de maintenir un client custom, on profite du polish de l’app officielle (Linux, Windows, macOS, mobile).

Pour la suite, et si l’équipe valide le POC : passer Headscale derrière un reverse proxy HTTPS avec un certificat valide, écrire des ACL pour cloisonner les utilisateurs, et brancher l’authentification sur l’OIDC de l’annuaire interne au lieu des preauth-keys.

Sources

Commencer la recherche

Saisissez des mots-clés pour rechercher des articles

↑↓
ESC
⌘K Raccourci