Cybersécurité

Headscale alternative self-hosted Tailscale : tutoriel 2026

12 min de lecture

Tailscale est merveilleux mais c’est un service hébergé chez Tailscale Inc. Pour les organisations qui veulent souveraineté complète, sécurité maximum, ou éviter les coûts pour grosses équipes, Headscale est une implémentation open-source du serveur de coordination Tailscale. Vous l’auto-hébergez sur votre VPS, vos clients Tailscale standard s’y connectent comme à Tailscale.com, et tout reste chez vous. Voici le tutoriel d’installation 2026.

Voir notre guide Tailscale pour les bases.

Pourquoi Headscale

  • Souveraineté complète : aucune donnée chez Tailscale Inc. Toutes vos clefs publiques, ACLs, sessions restent sur votre serveur.
  • Pas de coût par utilisateur : illimité pour le prix d’un VPS
  • Compatible clients Tailscale officiels : pas de fork, mêmes apps Linux/macOS/iOS/Android
  • Open-source BSD
  • Implémentation Go : binaire unique, simple à déployer

Limites vs Tailscale Cloud

  • Pas de dashboard web officiel inclus (mais headscale-ui communautaire existe)
  • Mises à jour à votre charge
  • SSO/OIDC à configurer manuellement
  • Pas de DERP relay automatique (vous devez en déployer un)
  • Logs et audit à gérer vous-même

Prérequis

  • Un VPS public avec IP fixe (Hetzner CX23 suffit largement)
  • Un domaine pointant sur le VPS (headscale.exemple.sn)
  • Reverse proxy HTTPS (Caddy ou Traefik)
  • Niveau attendu : avancé
  • Temps : 2-3 heures

Étape 1 — Installer Headscale

# Télécharger le binaire (vérifier la dernière version sur GitHub)
HEADSCALE_VERSION=0.23.0
wget -O /tmp/headscale.deb https://github.com/juanfont/headscale/releases/download/v${HEADSCALE_VERSION}/headscale_${HEADSCALE_VERSION}_linux_amd64.deb

sudo dpkg -i /tmp/headscale.deb
sudo systemctl enable headscale

Étape 2 — Configurer headscale.yaml

# /etc/headscale/config.yaml
server_url: https://headscale.exemple.sn
listen_addr: 0.0.0.0:8080
metrics_listen_addr: 127.0.0.1:9090
grpc_listen_addr: 0.0.0.0:50443
grpc_allow_insecure: false

private_key_path: /var/lib/headscale/private.key
noise:
  private_key_path: /var/lib/headscale/noise_private.key

prefixes:
  v4: 100.64.0.0/10
  v6: fd7a:115c:a1e0::/48

derp:
  server:
    enabled: false
  urls:
    - https://controlplane.tailscale.com/derpmap/default

database:
  type: sqlite3
  sqlite:
    path: /var/lib/headscale/db.sqlite

log:
  format: text
  level: info

dns:
  magic_dns: true
  base_domain: tailnet.exemple.sn
  nameservers:
    global:
      - 1.1.1.1
      - 8.8.8.8

Étape 3 — Reverse proxy Caddy

# /etc/caddy/Caddyfile
headscale.exemple.sn {
    reverse_proxy 127.0.0.1:8080

    @ws {
        header Connection *Upgrade*
        header Upgrade websocket
    }
    reverse_proxy @ws 127.0.0.1:8080
}
sudo systemctl restart caddy
sudo systemctl start headscale
sudo systemctl status headscale

Étape 4 — Créer un user et un device

# Créer un user
sudo headscale users create admin

# Générer une pre-auth key (clef d'inscription)
sudo headscale --user admin preauthkeys create --reusable --expiration 24h
# Copier la clef retournée

Étape 5 — Connecter un client Tailscale

# Sur la machine client (Linux/macOS)
sudo tailscale up \
  --login-server https://headscale.exemple.sn \
  --authkey LA_PRE_AUTH_KEY \
  --hostname laptop-1

# Vérifier
tailscale status

Sur Windows et iOS/Android, l’option login-server se configure dans les Settings/Préférences avancées de l’app Tailscale.

Étape 6 — ACLs Headscale

# /etc/headscale/acl_policy.json
{
  "groups": {
    "group:admins": ["admin"]
  },
  "tagOwners": {
    "tag:prod": ["group:admins"]
  },
  "acls": [
    {
      "action": "accept",
      "src": ["group:admins"],
      "dst": ["*:*"]
    }
  ],
  "ssh": [
    {
      "action": "accept",
      "src": ["group:admins"],
      "dst": ["tag:prod"],
      "users": ["root", "deploy"]
    }
  ]
}
# Référencer dans config.yaml
policy:
  mode: file
  path: /etc/headscale/acl_policy.json

sudo systemctl restart headscale

Étape 7 — Backup

# Backup quotidien de la base et des clefs
#!/bin/bash
DATE=$(date +%Y%m%d)
tar czf /tmp/headscale-$DATE.tar.gz /var/lib/headscale/ /etc/headscale/
aws s3 cp /tmp/headscale-$DATE.tar.gz s3://backups/headscale/
rm /tmp/headscale-$DATE.tar.gz

Headscale-UI (dashboard web)

Plusieurs UIs communautaires existent : headscale-ui, headplane. Déployables via Docker à côté de Headscale, accédant au gRPC sur le port 50443. Pratique pour les non-CLI.

Adaptation Afrique de l’Ouest

Pour une PME africaine qui veut souveraineté totale, Headscale sur un VPS local (chez Maktoob, DigitalRise) ou européen offre tout Tailscale sans dépendance externe. Coût : ~5 €/mois pour le VPS Headscale + 0 €/mois utilisateurs illimités. Excellent ROI face à 18 USD/user de Tailscale Premium.

Erreurs fréquentes

ErreurCauseSolution
« server_url is invalid »HTTPS non valideVérifier certificat Caddy/Let’s Encrypt
Tailscale client ne se connecte pasWebSocket non proxifiéAjouter handler @ws dans Caddyfile
Lent, latence élevéeDERP relay tiersDéployer son propre DERP
Magic DNS ne marche pasbase_domain mauvaisSous-domaine spécifique tailnet

Pour approfondir

Tester ce setup sur votre propre serveur

Le moyen le plus rapide de tester ce tutoriel en conditions réelles : prendre un petit VPS Hostinger.

Voir Hostinger →

Lien d affiliation. Si vous achetez via ce lien, le blog reçoit une petite commission sans surcoût pour vous.

Etape 1 : comprendre pourquoi Headscale plutot que Tailscale officiel

Tailscale offre un service hebergement gere par l’editeur americain, ce qui pose deux problemes pour un public ouest-africain : la latence vers les serveurs de coordination europeens et la dependance a un compte SaaS dont les conditions peuvent changer. Headscale est l’implementation libre du serveur de coordination Tailscale, ecrite en Go, qui se deploie sur votre propre VPS.

En 2026, Headscale a depasse la version 0.24 et supporte la quasi totalite des fonctionnalites du protocole : ACL, magic DNS, subnet routers, exit nodes, taildrop. Pour une PME a Dakar qui veut interconnecter trois bureaux et deux developpeurs nomades, c’est l’outil ideal.

Etape 2 : provisionner un VPS et installer Go

Choisissez un VPS Hostinger, OVH ou Scaleway situe a Paris pour minimiser la latence avec Dakar (autour de 60 ms). Le plan a 8 000 FCFA/mois suffit pour 100 noeuds. Connectez-vous en SSH puis installez Go 1.22 ou superieur.

sudo apt update && sudo apt install -y wget
wget https://go.dev/dl/go1.22.5.linux-amd64.tar.gz
sudo tar -C /usr/local -xzf go1.22.5.linux-amd64.tar.gz
echo 'export PATH=$PATH:/usr/local/go/bin' >> ~/.bashrc
source ~/.bashrc
go version
# Sortie attendue : go version go1.22.5 linux/amd64

Si la sortie affiche une version anterieure, Headscale refusera de compiler certaines fonctions liees a WireGuard. La presence du binaire go dans le PATH est la confirmation que la base est saine.

Etape 3 : installer Headscale via le paquet officiel

Plutot que de compiler depuis les sources, utilisez le paquet .deb publie sur le depot GitHub officiel. C’est plus rapide et la mise a jour se fait avec apt.

HEADSCALE_VERSION="0.24.0"
wget --output-document=headscale.deb   "https://github.com/juanfont/headscale/releases/download/v${HEADSCALE_VERSION}/headscale_${HEADSCALE_VERSION}_linux_amd64.deb"
sudo apt install -y ./headscale.deb
sudo systemctl enable --now headscale
systemctl status headscale

La sortie attendue affiche active (running) en vert. Si vous voyez failed, consultez journalctl -u headscale -n 50 pour identifier l’erreur (souvent un port deja occupe ou une mauvaise config TLS).

Etape 4 : configurer le fichier /etc/headscale/config.yaml

Le fichier de configuration vit dans /etc/headscale/config.yaml. Editez-le pour pointer vers votre nom de domaine et activer HTTPS via Let’s Encrypt. Remplacez vpn.exemple.sn par votre propre domaine.

server_url: https://vpn.exemple.sn:443
listen_addr: 0.0.0.0:8080
metrics_listen_addr: 127.0.0.1:9090
private_key_path: /var/lib/headscale/private.key
ip_prefixes:
  - 100.64.0.0/10
  - fd7a:115c:a1e0::/48
database:
  type: sqlite3
  sqlite:
    path: /var/lib/headscale/db.sqlite
dns:
  magic_dns: true
  base_domain: vpn.exemple.sn

Sauvegardez puis relancez avec sudo systemctl restart headscale. Une erreur de syntaxe YAML est la cause la plus frequente d’echec a ce stade : verifiez l’indentation a deux espaces, jamais de tabulation.

Etape 5 : exposer Headscale derriere Caddy avec TLS automatique

Headscale ecoute en HTTP sur le port 8080. Pour TLS, le plus simple est de placer Caddy en reverse proxy. Caddy obtient et renouvelle automatiquement le certificat Let’s Encrypt sans configuration supplementaire.

# /etc/caddy/Caddyfile
vpn.exemple.sn {
  reverse_proxy localhost:8080
}

Lancez sudo systemctl reload caddy puis testez avec curl -I https://vpn.exemple.sn. Vous devez recevoir un code 200 et un en-tete server: Caddy. Si le certificat echoue, verifiez que les ports 80 et 443 sont ouverts dans le pare-feu UFW ou dans le panneau Hostinger.

Etape 6 : creer un utilisateur et generer une cle de pre-authentification

Headscale organise les noeuds par utilisateur (au sens namespace, pas authentification). Creez un utilisateur pour l’equipe technique et generez une cle reutilisable.

sudo headscale users create equipe-dakar
sudo headscale --user equipe-dakar preauthkeys create --reusable --expiration 24h
# Sortie attendue : un token de 64 caracteres hex

Copiez ce token : il sera utilise sur chaque appareil pour rejoindre le reseau sans interaction manuelle. La duree de 24h est un bon compromis entre securite et confort de deploiement sur le terrain.

Etape 7 : connecter un client Linux au reseau

Sur un poste Ubuntu, installez le client Tailscale officiel (qui parle au serveur Headscale sans modification) puis enregistrez-le avec votre token.

curl -fsSL https://tailscale.com/install.sh | sh
sudo tailscale up --login-server https://vpn.exemple.sn   --authkey VOTRE_TOKEN
tailscale status
# Sortie attendue : la liste des noeuds avec leur IP 100.64.x.x

Si tailscale status renvoie not logged in, relancez tailscale up en verifiant l’URL du login-server. C’est l’erreur la plus commune : oublier le https:// ou laisser un slash final.

Etape 8 : ajouter un smartphone Android

Sur Android, l’application Tailscale ne permet pas par defaut de specifier un serveur custom. Solution : activer l’option developpeur en tapant 7 fois sur la version dans les parametres de l’app, puis renseigner l’URL https://vpn.exemple.sn. Connectez-vous via le QR code genere cote serveur.

sudo headscale --user equipe-dakar nodes register --key NODEKEY
# La cle NODEKEY est affichee par l'app au moment de la connexion

Une fois enregistre, le telephone apparait dans tailscale status avec une IP de la plage 100.64.0.0/10. Le tunnel WireGuard est etabli, vous pouvez ssh-er sur le serveur depuis votre telephone meme via une connexion 4G Orange ou Free.

Etape 9 : ecrire des regles ACL pour cloisonner les acces

Par defaut, Headscale autorise tous les noeuds a communiquer entre eux. Pour une entreprise, ce n’est pas acceptable : un poste developpeur ne doit pas atteindre la base de donnees de production. Ecrivez un fichier ACL au format HuJSON.

// /etc/headscale/acl.hujson
{
  "groups": {
    "group:devs": ["alice@", "bob@"],
    "group:ops":  ["carol@"]
  },
  "acls": [
    { "action": "accept", "src": ["group:ops"],  "dst": ["*:*"] },
    { "action": "accept", "src": ["group:devs"], "dst": ["100.64.0.10:22,80,443"] }
  ]
}

Activez le fichier dans config.yaml via policy.path: /etc/headscale/acl.hujson puis redemarrez. Testez ensuite depuis un poste dev qu’il ne peut pas pinger une IP non autorisee : c’est la confirmation que la segmentation est effective.

Etape 10 : sauvegarder la base SQLite et les cles

La perte de /var/lib/headscale/db.sqlite ou de la cle privee oblige a re-enrôler tous les appareils. Programmez une sauvegarde quotidienne sur un stockage externe (Backblaze B2, Wasabi, ou un VPS secondaire en Allemagne).

cat > /usr/local/bin/headscale-backup.sh <<'EOF'
#!/bin/bash
DATE=$(date +%Y%m%d)
sqlite3 /var/lib/headscale/db.sqlite ".backup /tmp/hs-$DATE.db"
rclone copy /tmp/hs-$DATE.db b2:itsc-backup/headscale/
rm /tmp/hs-$DATE.db
EOF
chmod +x /usr/local/bin/headscale-backup.sh

Ajoutez ensuite la ligne 0 3 * * * /usr/local/bin/headscale-backup.sh dans crontab -e. La commande .backup de SQLite est sure meme avec le serveur en marche, contrairement a un simple cp.

Etape 11 : surveiller la sante du service avec Prometheus

Headscale expose des metriques Prometheus sur le port 9090. Branchez un Prometheus local et un dashboard Grafana pour visualiser le nombre de noeuds connectes, le trafic relai DERP et les erreurs.

# prometheus.yml
scrape_configs:
  - job_name: 'headscale'
    static_configs:
      - targets: ['127.0.0.1:9090']

Apres rechargement, l’onglet Targets de Prometheus doit afficher Headscale en vert. Importez ensuite le dashboard Grafana ID 18030 pour avoir une vue prete a l’emploi en moins de cinq minutes.

Etape 12 : aller plus loin avec exit nodes et subnet routers

Un exit node permet de router tout le trafic Internet d’un client a travers un noeud du reseau (utile pour acceder a un service geo-restreint). Un subnet router expose un sous-reseau local (le LAN du bureau de Dakar par exemple) aux autres membres.

# Sur le noeud qui sert de subnet router
sudo tailscale up --login-server https://vpn.exemple.sn   --authkey TOKEN   --advertise-routes=192.168.1.0/24
# Cote serveur Headscale
sudo headscale routes enable -r 1

Verifiez avec tailscale status --json sur un client distant que la route est bien apprise. Vous pouvez maintenant acceder a un NAS interne depuis Abidjan ou Lome comme si vous etiez physiquement dans le bureau de Dakar. Pour heberger votre code source en parallele, voyez notre guide Forgejo, et pour optimiser le frontend qui consomme ces APIs internes, le tutoriel Astro Server Islands.

Etape 13 : automatiser le renouvellement des cles d’authentification

Les preauthkeys reutilisables expirent. Pour un parc de 50 appareils sur le terrain a Thies ou Saint-Louis, generer manuellement une nouvelle cle chaque semaine est intenable. Ecrivez un petit script qui regenere le token et l’envoie via un canal securise (signal, mattermost interne).

#!/bin/bash
NEW_KEY=$(sudo headscale --user equipe-dakar preauthkeys create \
  --reusable --expiration 168h --output json | jq -r '.key')
echo "Nouvelle cle valide 7 jours : $NEW_KEY" | \
  mail -s "Cle Headscale hebdomadaire" admin@exemple.sn

Place dans cron tous les lundis a 6h, ce script garantit qu’une cle valide est toujours disponible sans intervention humaine. La sortie attendue est un email recu dans la boite admin contenant la nouvelle cle.

Etape 14 : tester la resilience en coupant le serveur

Une qualite peu connue de Tailscale et donc de Headscale : le tunnel direct entre noeuds continue a fonctionner meme si le serveur de coordination tombe. Validez ce comportement avant de passer en production.

# Sur le serveur Headscale
sudo systemctl stop headscale
# Sur un client deja connecte
ping 100.64.0.5
# Reponse : ok, le tunnel reste actif
sudo systemctl start headscale

Cette demonstration rassure les equipes : meme en cas de panne du VPS principal, les communications etablies entre bureaux ne sont pas interrompues. Seuls les nouveaux enrôlements echoueront tant que le service est down.

Partager