Headscale #
Headscale ist eine quelloffene, selbst gehostete Implementierung des Tailscale Control Servers. Es ermöglicht Ihnen, Ihr eigenes Tailscale-Netzwerk zu betreiben und somit die vollständige Kontrolle über Ihre Knoten und Daten zu haben.
Quelle #
Das Projekt basiert auf der offiziellen Headscale-Website. Das Helm-Chart stammt aus der OCI-Registry codeberg.org/wrenix/helm-charts.
Dokumentation #
Funktion #
Dieser Dienst bietet einen selbst gehosteten Tailscale Control Server, der die Verwaltung von Tailscale-Knoten und die Einrichtung eines privaten Tailscale-Netzwerks ermöglicht. Er unterstützt die Integration mit Cert-Manager für TLS-Zertifikate und MetalLB für LoadBalancer-IPs.
Lokale Anpassungen #
- Die
values.yaml-Datei konfiguriert den Headscale-Server, einschließlich der Listen-Adressen, Server-URL und DNS-Einstellungen. - Die Persistenz läuft über die StorageClass
longhorn(siehestorage.yaml). - Es sind Patches für den DERP-Dienst enthalten, um Dual-Stack (IPv4/IPv6)-Unterstützung zu ermöglichen.
- TLS-Zertifikate werden über Cert-Manager verwaltet.
- Private Schlüssel für WireGuard, Noise und DERP werden als Secrets generiert.
Wichtige Einstellungen #
- Server URL: Die öffentliche URL des Headscale-Servers steht als
server_urlinvalues.yaml. Sie muss exakt der Adresse entsprechen, unter der die Knoten den Server erreichen — ein späterer Wechsel zwingt jeden Knoten zur Neuanmeldung. - Datenbank: Headscale verwendet eine SQLite-Datenbank, die auf einem persistenten Volume gespeichert wird.
- Zertifikate: TLS-Zertifikate für den Ingress werden automatisch von
cert-managerausgestellt. - DNS: Knoten im Tailnet erhalten Namen unterhalb der in
base_domaingesetzten Domain. Sie darf sich nicht mit einer real aufgelösten Domain überschneiden. - Adress-Präfixe:
fd7a:115c:a1e0::/48(v6) und100.64.0.0/23(v4) — nicht auf ein eigenes Präfix ändern. Headscale unterstützt nur Teilmengen der Standardbereiche (100.64.0.0/10,fd7a:115c:a1e0::/48) und warnt bei allem anderen beim Start mit „This is NOT a supported configuration". Der Grund ist praktisch: Seit Headscale 0.29.0 (PR #3036) expandiert*alssrc/dstin der ACL auf genau diese Standardbereiche statt auf0.0.0.0/0/::/0. Liegt das eigene v6-Präfix außerhalb, enthält der kompilierte Paketfilter keine passende v6-Quelle und jedes IPv6-Paket wird am Ingress des Subnet-Routers verworfen — bei v4 fällt es nicht auf, solange das Präfix eine Teilmenge von100.64.0.0/10ist. Hier doppelt abgesichert, da die ACL zusätzlichgroup:/tag:statt*verwendet. Im CDP-Cluster war mitfd72:a312:5995::/48und"src":["*"]genau dieser Ausfall eingetreten; dort wurde am 31.08.2026 auffd7a:115c:a1e0:2::/64migriert. Getestet mit Headscale 0.29.3. - ACL-Policy: Liegt in der Datenbank, nicht in der Konfigurationsdatei (
policy.mode: database). Änderungen anconfig-patch.yamlwerden durch ArgoCD zwar ins Secret synchronisiert, aber nicht wirksam — dafür ist ein expliziterheadscale policy setnötig. Siehe ACL-Policy setzen. - DERP: Es werden die öffentlichen DERP-Server von Tailscale verwendet. Ein eigener DERP-Service ist konfiguriert, aber nicht als Server aktiviert. Der STUN-Dienst läuft über einen LoadBalancer-Service.
- Speicher: Konfiguration und SQLite-Datenbank liegen auf einem Longhorn-Volume. Diese Datenbank ist der gesamte Zustand des Tailnets — geht sie verloren, müssen sich alle Knoten neu anmelden.
ACL-Policy setzen #
policy.mode steht auf database: Die wirksame Policy liegt in der Headscale-Datenbank, nicht in
der Konfigurationsdatei. config-patch.yaml patcht das Secret headscale, das im Pod als
/etc/headscale/policy.json gemountet wird — Headscale liest diese Datei aber weder beim Start noch
bei einem Neustart ein.
Ein Commit an config-patch.yaml ändert am laufenden System also nichts. ArgoCD meldet Synced,
das Secret ist aktuell, die Datei im Pod ist aktuell — und die Policy im Cluster ist trotzdem die
alte. Das Repo ist für die ACL nicht die Wahrheit.
Nach jeder Änderung an config-patch.yaml ist deshalb dieser Schritt nötig:
1kubectl exec -n headscale deploy/headscale -- headscale policy set -f /etc/headscale/policy.jsonVorher sicherstellen, dass ArgoCD das Secret bereits synchronisiert hat — sonst schreibt der Befehl den alten Stand erneut in die Datenbank. Der Pod muss dafür nicht neu gestartet werden.
Wirksamen Stand ansehen:
1kubectl exec -n headscale deploy/headscale -- headscale policy getAuf Drift prüfen — die Ausgabe von policy get gegen den policy.json-Block in config-patch.yaml
halten. Weichen sie ab, wurde eine Änderung committet, aber nie gesetzt.
Verifizieren, dass die Clients die neue Policy haben. Der kompilierte Paketfilter erscheint binnen Sekunden auf den Knoten; ein Subnet-Router eignet sich am besten dafür:
1kubectl exec -n wireguard deploy/wireguard-tailscale -c tailscale -- tailscale debug netmap | jq ".PacketFilter[].Srcs"Installation #
Die Anwendung wird mittels Kustomize und Helm durch ArgoCD im Kubernetes-Cluster bereitgestellt. Die Konfiguration befindet sich im apps/headscale-Verzeichnis. Eine manuelle Installation kann mit folgendem Befehl durchgeführt werden:
1kubectl kustomize --enable-helm apps/headscale | kubectl apply -n headscale -f -Abhängigkeiten #
- Ein laufender Kubernetes-Cluster.
- Ein Ingress-Controller (z.B. Traefik) für den externen Zugriff.
- Eine Zertifikatsmanagement-Lösung (z.B. cert-manager) zur Bereitstellung von TLS-Zertifikaten.
- MetalLB für LoadBalancer-IP-Zuweisung.
- Longhorn als Storage-Provider für die PersistentVolumes.