11 KiB
Tailscale2Headscale
Headscale + Tailscale – Homelab Migration
⚠️ Version publique / anonymisée Tous les noms de domaine, IP et clés ont été remplacés par des placeholders. Remplace toutes les valeurs
<...>par les tiennes avant d’utiliser ce document.
1. Objectif
Mettre en place un contrôle Tailscale auto‑hébergé avec Headscale pour remplacer le control plane Tailscale SaaS, tout en :
- gardant les clients Tailscale classiques (Linux, macOS, Android, Home Assistant, etc.) ;
- centralisant la gestion sur un serveur Headscale accessible en HTTPS ;
- évitant les bugs classiques (mauvais port, mauvais
--login-server, clés expirées, etc.).
Ce README récapitule :
- l’architecture retenue ;
- la configuration Headscale (service + Web admin + reverse proxy) ;
- la configuration des clients Tailscale ;
- les problèmes rencontrés et les solutions pour ne plus tomber dedans.
2. Architecture globale
2.1. Composants
-
Serveur Headscale
- Type : LXC / VM Linux (Debian‑like)
- Rôle : serveur
headscale+ Web UI (headscale‑admin) - IP locale :
<HEADSCALE_LAN_IP>(ex :192.168.1.xxx) - Nom interne :
<HEADSCALE_HOSTNAME>(ex :headscale)
-
Reverse proxy (facultatif mais recommandé)
- Logiciel :
caddy(ou nginx/traefik) - Port HTTP interne Headscale :
8080 - Port HTTPS public :
443 - Nom de domaine :
<HEADSCALE_PUBLIC_FQDN>(ex :headscale.example.com) - Certificats : Let’s Encrypt ou équivalent
- Logiciel :
-
Clients Tailscale
- Serveur Proxmox VE :
<PVE_HOST> - Proxmox Backup Server :
<PBS_HOST> - NAS (OMV ou autre) :
<NAS_HOST> - Containers / VM :
<CT_DOCKER_PTR>,<CT_JENKINS>, etc. - Home Assistant : Add-on Tailscale
- macOS : client Tailscale officiel
- Android : app Tailscale officielle
- Serveur Proxmox VE :
Tout ce petit monde parle au contrôle plane : https://<HEADSCALE_PUBLIC_FQDN>.
3. Installation de Headscale (serveur)
3.1. Pré‑requis
Sur la machine Headscale (LXC/VM) :
apt update
apt install -y headscale sqlite3 ca-certificates curl
Créer le répertoire de configuration :
mkdir -p /etc/headscale
3.2. Fichier de configuration config.yaml (exemple)
⚠️ Important : Adapter les valeurs
server_url,listen_addr,grpc_listen_addr,db_path, etc.
# /etc/headscale/config.yaml
server_url: "https://<HEADSCALE_PUBLIC_FQDN>"
listen_addr: "0.0.0.0:8080"
grpc_listen_addr: "0.0.0.0:50443"
ip_prefixes:
- "100.64.0.0/10"
derp:
server:
enabled: true
region_id: 999
region_code: "homelab"
region_name: "Homelab DERP"
paths: []
# Base SQLite (simple à gérer pour un homelab)
db_type: "sqlite"
db_path: "/var/lib/headscale/db.sqlite"
tls_cert_path: ""
tls_key_path: ""
log:
level: "info"
dns_config:
override_local_dns: false
magic_dns: false
3.3. Service systemd
Le paquet Debian fournit généralement un service headscale.service.
Vérifier :
systemctl enable --now headscale
systemctl status headscale --no-pager
4. Web admin : headscale‑admin
4.1. Dossier et configuration
Headscale‑admin est en général servi par un serveur web (caddy/nginx), souvent depuis :
/var/www/headscale-admin
ou :
/opt/headscale-admin
Cette UI se connecte à l’API Headscale (port 8080 dans notre cas).
5. Reverse proxy (Caddy)
C’est ici qu’on a eu une galère : mélange de ports
80et8080. Résultat : clients Tailscale qui pointaient sur:8080alors que le proxy écoutait en:80→ 404 ou connexion impossible.
5.1. Exemple de config Caddy
# /etc/caddy/Caddyfile
<HEADSCALE_PUBLIC_FQDN> {
encode gzip
# Proxy API Headscale (port interne 8080)
reverse_proxy /api/* 127.0.0.1:8080
# UI headscale-admin statique
root * /var/www/headscale-admin
file_server
}
5.2. Points critiques
- Le serveur Headscale écoute sur
:8080. - Le reverse proxy écoute en :443 (et :80 pour HTTP → redirect vers 443).
- Les clients Tailscale n’ont pas besoin du port dans
--login-servers’ils passent par HTTPS standard :--login-server=https://<HEADSCALE_PUBLIC_FQDN> - Si tu décides de garder un port non standard (
:8443par ex.), il faut le mettre partout :- dans
server_urlde Headscale ; - dans
--login-server=côté clients ; - dans la config du reverse proxy.
- dans
6. Gestion des utilisateurs et clés dans Headscale
6.1. Créer un user
headscale users create <USERNAME>
headscale users list
Exemple :
headscale users create syleric
6.2. Créer une preauth key
headscale preauthkeys create --user <USER_ID> --reusable --ephemeral=false
Exemple :
headscale users list
# ID | Name | Username | Email | Created
# 1 | | $user | | ...
headscale preauthkeys create --user 1 --reusable --ephemeral=false
# -> renvoie une clé du type : 548fd8... (ne pas mettre ici en clair)
🔁 Si tu doutes d’une clé, tu peux la regénérer et supprimer l’ancienne.
7. Enregistrement des clients Tailscale
7.1. Schéma global
Pour chaque machine, on fait :
tailscale logout || true
tailscale down || true
tailscale up \
--login-server=https://<HEADSCALE_PUBLIC_FQDN> \
--auth-key=<PREAUTH_KEY> \
--accept-dns=false \
--accept-routes=false \
--reset
On utilise
--accept-dns=falsepour éviter que Headscale override le DNS local du homelab, et--accept-routes=falsetant qu’on ne met pas en place de routes subnets poussées via Headscale.
8. Cas particulier : macOS
8.1. Utiliser le binaire Tailscale macOS
Depuis le Mac :
/Applications/Tailscale.app/Contents/MacOS/Tailscale status
/Applications/Tailscale.app/Contents/MacOS/Tailscale logout || true
/Applications/Tailscale.app/Contents/MacOS/Tailscale down || true
Puis :
sudo /Applications/Tailscale.app/Contents/MacOS/Tailscale up \
--login-server=https://<HEADSCALE_PUBLIC_FQDN> \
--auth-key=<PREAUTH_KEY> \
--accept-dns=false \
--accept-routes=false \
--reset
Si la clé est valide et le --login-server accessible, tu dois voir :
/Applications/Tailscale.app/Contents/MacOS/Tailscale status
# 100.64.0.X <MAC_HOSTNAME> <USER> macOS -
9. Cas particulier : Home Assistant (add-on Tailscale)
9.1. Configurer l’add-on
Dans la configuration de l’add-on Tailscale (interface Home Assistant) :
authkey: "<PREAUTH_KEY>"
hostname: "homeassistant"
login_server: "https://<HEADSCALE_PUBLIC_FQDN>"
userspace_networking: true # ou false selon ton besoin
accept_dns: false
accept_routes: false
Ensuite, côté Headscale, tu verras un node :
headscale nodes list
# ...
# homeassistant | homeassistant | ... | 100.64.0.9, ...
Si l’expiration a été mise par erreur, tu peux la corriger :
headscale nodes expire --identifier <NODE_ID> --expiry 0001-01-01T00:00:00Z
⚠️ Attention : commande
expiresert à fixer une date d’expiration. Pour ne pas expirer, il faut utiliser la valeur spéciale0001-01-01T00:00:00Z.
10. Android (smartphone & tablette)
10.1. Changer de serveur de login
Sur Android, il n’y a pas (encore) de bouton clair dans l’UI pour changer de login-server vers un Headscale custom.
La méthode fiable :
-
Se déconnecter de Tailscale sur l’app (logout).
-
Effacer les données de l’appli (ou désinstaller/réinstaller) pour repartir de zéro.
-
Lors du premier
tailscale uppar CLI dans Termux (si tu utilises Termux + binaire Linux), lancer :tailscale up \ --login-server=https://<HEADSCALE_PUBLIC_FQDN> \ --auth-key=<PREAUTH_KEY> \ --accept-dns=false \ --accept-routes=false \ --reset -
Côté Headscale, tu verras apparaître le nouveau node avec hostname (souvent
localhostsi tu utilises Termux) ; tu peux ensuite le renommer côté Headscale :headscale nodes rename --identifier <NODE_ID> <NOM_SOUHAITÉ>
💡 Attention : renommer dans Headscale ne change pas le hostname système Android/Termux, mais ça rend la liste Headscale plus lisible.
11. Vérifications globales
11.1. Depuis Headscale
headscale nodes list
Tu dois voir quelque chose du genre :
ID | Hostname | Name | IP addresses | Connected | Expired
1 | jenkins | jenkins | 100.64.0.1, fd7a:115c:a1e0::1 | online | no
2 | docker-ptr | docker-ptr | 100.64.0.2, fd7a:115c:a1e0::2 | online | no
3 | nas | nas | 100.64.0.5, fd7a:115c:a1e0::5 | online | no
...
11.2. Depuis un client (ex : PBS ou PVE)
tailscale status
Tu dois voir tous les peers (Mac, Android, HA, etc.) avec leurs IPs 100.64.0.X.
12. Pièges rencontrés & leçons apprises
12.1. Mauvais port dans le reverse proxy
- Symptôme :
curl http://127.0.0.1:8080→ 404 ou rien- Tailscale
upsemble bloquer ou ne jamais arriver à joindre le serveur
- Cause :
- Caddy/nginx servait sur le port
80, mais on essayait d’appeler:8080depuis l’extérieur.
- Caddy/nginx servait sur le port
- Solution :
- Standardiser :
- Headscale API :
:8080(interne) - Reverse proxy :
:443(public) --login-server=https://<HEADSCALE_PUBLIC_FQDN>(pas de port explicite si 443)
- Headscale API :
- Standardiser :
12.2. Mélange Tailscale SaaS / Headscale
- Symptôme :
- Le client Tailscale ouvre une URL
https://login.tailscale.com/...
- Le client Tailscale ouvre une URL
- Cause :
- Pas de
--login-serverou valeur incorrecte → le client repart sur le SaaS officiel.
- Pas de
- Solution :
- Toujours vérifier la commande
tailscale up:tailscale up --login-server=https://<HEADSCALE_PUBLIC_FQDN> ...
- Toujours vérifier la commande
12.3. Expiration des nodes
- Symptôme :
- Dans
headscale nodes list, la colonneExpiredpasse àyes.
- Dans
- Cause :
- Mauvaise manipulation de la commande
expireavec une date proche du présent.
- Mauvaise manipulation de la commande
- Solution :
- Pour un node qui ne doit pas expirer :
headscale nodes expire --identifier <ID> --expiry 0001-01-01T00:00:00Z
- Pour un node qui ne doit pas expirer :
13. Résumé rapide (mode pense‑bête)
-
Headscale
- Config dans
/etc/headscale/config.yaml listen_addr: 0.0.0.0:8080server_url: "https://<HEADSCALE_PUBLIC_FQDN>"
- Config dans
-
Reverse proxy
- Terminate TLS (Let’s Encrypt)
- Proxy
/api→127.0.0.1:8080 - Servir la Web UI statique (
/var/www/headscale-admin)
-
Utilisateur & clés
headscale users create <USERNAME>headscale preauthkeys create --user <ID> --reusable --ephemeral=false
-
Clients
- Commande type :
tailscale logout || true tailscale down || true tailscale up \ --login-server=https://<HEADSCALE_PUBLIC_FQDN> \ --auth-key=<PREAUTH_KEY> \ --accept-dns=false \ --accept-routes=false \ --reset
- Commande type :
-
Validation
headscale nodes listtailscale statusdepuis plusieurs machines
Avec cette checklist, tu peux reconstruire tout ton tailnet Headscale sans re‑te prendre 2 jours de galère, et tu peux publier ce README (après remplacement des placeholders) sur un dépôt public sans exposer ta vraie infra.