Headscale auf Debian 13 selbst hosten: Open-Source-Coordinator für Tailscale-VPN. Volle Kontrolle über euer Mesh-Netzwerk ohne Drittanbieter-Abhängigkeit.
💡 Hinweis: Dieses Tutorial setzt voraus, dass du einen normalen Benutzer mit sudo-Rechten verwendest — wie im Tutorial Debian 13 Server absichern beschrieben. Außerdem solltest du einen Reverse Proxy mit Nginx eingerichtet haben.
Einleitung — Was ist Headscale?
Wer Tailscale genutzt hat, weiß: Es ist die wahrscheinlich einfachste Mesh-VPN-Lösung überhaupt. Geräte verbinden sich automatisch, NAT-Traversal funktioniert, Routing wird automatisch berechnet. Aber: Der Coordination Server läuft bei Tailscale Inc. Eure Metadaten — wer mit wem reden will, wann, wie lange — gehen über deren Infrastruktur.
Headscale ist ein Open-Source-Reimplementation des Tailscale-Coordinators. Ihr hostet ihn selbst — die Tailscale-Clients verbinden sich nicht mehr zu controlplane.tailscale.com, sondern zu eurem eigenen Server. Resultat: Volle Kontrolle, keine Drittanbieter-Abhängigkeit, kostenlos für unbegrenzt viele Nodes.
Die eigentliche Datenübertragung zwischen Geräten passiert weiterhin direkt (Peer-to-Peer) — der Coordinator vermittelt nur Schlüssel und Routen. Eure Daten gehen nie über den Headscale-Server.
Was Headscale kann
- Vollständig kompatibel mit den offiziellen Tailscale-Clients (Linux, macOS, Windows, iOS, Android)
- Mesh-VPN wie bei Tailscale — direkte Verbindungen, NAT-Traversal
- Magic DNS — Geräte per Hostnamen erreichbar
- ACLs für feingranulare Zugriffsregeln
- Subnet Routing — Komplette LAN-Subnetze freigeben
- Exit-Nodes — Internet-Traffic über bestimmtes Gerät routen
- Multi-User — Mit Namespaces für getrennte Netze
- Web-UI über Drittanbieter wie
headscale-uioderheadplane
Headscale vs. Tailscale Cloud
| Feature | Headscale | Tailscale Cloud |
|---|---|---|
| Setup-Komplexität | Mittel-Hoch | Trivial |
| Kontrolle über Coordinator | Voll | Keine |
| Kosten | Selbsthost-Kosten | Free bis 100 Geräte, dann $$$ |
| Geräte-Limit | Unbegrenzt | 100 (Free) |
| Web-Admin-UI | Nur Drittanbieter | Eingebaut |
| Tailnet-Lock | Nein (geplant) | Ja |
| MagicDNS | Ja | Ja |
| ACLs | Ja | Ja |
| Beste Wahl für | Privacy-Fanatiker, große Setups | Schnell-Setups, Standard-User |
⚠️ Wichtig: Headscale braucht einen öffentlich erreichbaren Server mit gültigem SSL-Zertifikat. Tailscale-Clients akzeptieren keine self-signed Certificates oder IP-Adressen. Eine Domain ist Pflicht.
In diesem Guide richten wir Headscale auf Debian 13 ein, verbinden mehrere Clients und konfigurieren ACLs.
Voraussetzungen
- Debian 13 Server mit öffentlicher IP — siehe Debian 13 Server-Guide
- Eine Domain (z.B.
headscale.example.com) - Nginx als Reverse Proxy
certbotinstalliert- Mindestens 1 GB RAM (Headscale ist sehr leichtgewichtig)
Firewall & DNS
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw allow 3478/udp # STUN für NAT-Traversal
sudo ufw statusDNS-A-Record für headscale.example.com setzen.
Schritt 1: Headscale installieren
Wir nutzen die offiziellen .deb-Pakete:
HEADSCALE_VERSION=$(curl -s https://api.github.com/repos/juanfont/headscale/releases/latest | grep tag_name | cut -d '"' -f 4)
echo "Installiere Headscale $HEADSCALE_VERSION"
cd /tmp
wget "https://github.com/juanfont/headscale/releases/download/${HEADSCALE_VERSION}/headscale_${HEADSCALE_VERSION#v}_linux_amd64.deb"
sudo dpkg -i "headscale_${HEADSCALE_VERSION#v}_linux_amd64.deb"Headscale wird automatisch als systemd-Service eingerichtet, aber noch nicht gestartet — wir müssen erst die Konfiguration anpassen.
headscale versionSollte eine Versionsnummer ausgeben.
Schritt 2: Konfiguration
sudo nano /etc/headscale/config.yamlWichtige Einstellungen anpassen:
# Eure öffentliche Server-URL (mit https!)
server_url: https://headscale.example.com
# IP-Adresse für Headscale (intern, hinter Nginx)
listen_addr: 127.0.0.1:8080
# Metrics-Endpoint
metrics_listen_addr: 127.0.0.1:9090
# gRPC für CLI-Verwaltung
grpc_listen_addr: 127.0.0.1:50443
grpc_allow_insecure: <strong>false</strong>
# Datenbank — SQLite für kleine Setups, PostgreSQL für große
database:
type: sqlite
sqlite:
path: /var/lib/headscale/db.sqlite
# Private Key — wird automatisch generiert
noise:
private_key_path: /var/lib/headscale/noise_private.key
# Tailnet-Adressbereich (RFC1918, nicht euer LAN!)
prefixes:
v4: 100.64.0.0/10
v6: fd7a:115c:a1e0::/48
# DERP-Server (für NAT-Traversal)
derp:
server:
enabled: <strong>false</strong>
region_id: 999
region_code: "headscale"
region_name: "Headscale Embedded DERP"
stun_listen_addr: "0.0.0.0:3478"
urls:
- https://controlplane.tailscale.com/derpmap/default
paths: []
auto_update_enabled: <strong>true</strong>
update_frequency: 24h
# Magic DNS
dns:
magic_dns: <strong>true</strong>
base_domain: example.com
override_local_dns: <strong>true</strong>
nameservers:
global:
- 1.1.1.1
- 8.8.8.8
# Logs
log:
level: info
format: text
# OIDC (optional — falls Authentik vorhanden)
# oidc:
# issuer: "https://auth.example.com/application/o/headscale/"
# client_id: "EURE_CLIENT_ID"
# client_secret: "EUER_CLIENT_SECRET"
# Policy / ACLs (kommen später)
policy:
mode: file
path: "/etc/headscale/acl.yaml"
# Unix-Socket für lokale Verwaltung
unix_socket: /var/run/headscale/headscale.sock
unix_socket_permission: "0770"⚠️ Wichtig: Der Adressbereich
100.64.0.0/10ist Tailscale-Standard — nicht euer LAN! Verwendet niemals einen Bereich, der mit eurem normalen Netz kollidiert (192.168.x.x,10.x.x.x,172.16-31.x.x).
Verzeichnisse anlegen
sudo mkdir -p /var/lib/headscale /var/run/headscale
sudo chown -R headscale:headscale /var/lib/headscale /var/run/headscaleSchritt 3: Headscale starten
sudo systemctl enable --now headscale
sudo systemctl status headscaleLogs mitlesen:
sudo journalctl -u headscale -fBei listening and serving HTTP on 127.0.0.1:8080 ist Headscale bereit.
Funktion testen
sudo headscale version
sudo headscale users listSollte eine leere User-Liste zeigen.
Schritt 4: Nginx Reverse Proxy
sudo nano /etc/nginx/sites-available/headscale.example.comInhalt:
server {
listen 80;
listen [::]:80;
server_name headscale.example.com;
location /.well-known/acme-challenge/ {
root /var/www/html;
}
location / {
return 301 https://$host$request_uri;
}
}
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name headscale.example.com;
ssl_certificate /etc/letsencrypt/live/headscale.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/headscale.example.com/privkey.pem;
# Lange Timeouts für persistente Tailscale-Verbindungen
proxy_read_timeout 1d;
proxy_send_timeout 1d;
location / {
proxy_pass http://127.0.0.1:8080;
proxy_http_version 1.1;
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_set_header X-Forwarded-Proto $scheme;
# WebSocket / Long-Polling Support
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $http_connection;
proxy_buffering off;
proxy_redirect off;
}
}Aktivieren und SSL holen:
sudo ln -s /etc/nginx/sites-available/headscale.example.com /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
sudo certbot --nginx -d headscale.example.comTest:
curl https://headscale.example.com/healthSollte OK zurückgeben.
✅ Geschafft! Headscale läuft öffentlich erreichbar mit SSL. Jetzt verbinden wir die ersten Clients.
Schritt 5: Ersten User anlegen
Headscale-User sind Namespaces — keine Logins. Sie gruppieren Geräte:
sudo headscale users create mariusUser-Liste prüfen:
sudo headscale users listIhr seht jetzt euren neuen User mit einer ID.
Schritt 6: Ersten Tailscale-Client verbinden
Tailscale-Client auf eurem Laptop / Server installieren — z.B. auf einem zweiten Linux-System:
curl -fsSL https://tailscale.com/install.sh | sudo shDann statt der Tailscale-Cloud auf eure Headscale-Instanz zeigen lassen:
sudo tailscale up --login-server=https://headscale.example.comTailscale öffnet eine URL in einer Form, die wie folgt aussieht:
To authenticate, visit:
https://headscale.example.com/register/nodekey:abc123...Diese URL öffnet ihr nicht im Browser — sondern kopiert den nodekey:abc123...-Teil und registriert ihn auf dem Headscale-Server:
sudo headscale nodes register --user marius --key nodekey:abc123...Auf dem Client läuft Tailscale jetzt los.
Status prüfen
Auf dem Headscale-Server:
sudo headscale nodes listSollte euren neuen Node zeigen.
Auf dem Client:
tailscale status
tailscale ipEure Tailscale-IP sollte aus dem 100.64.0.0/10-Bereich kommen.
Schritt 7: Pre-Authenticated Keys für einfaches Onboarding
Statt jeden Node manuell zu registrieren, könnt ihr Pre-Auth-Keys generieren:
sudo headscale preauthkeys create --user marius --reusable --expiration 24hOutput:
abc123def456...Auf jedem neuen Client:
sudo tailscale up --login-server=https://headscale.example.com --auth-key=abc123def456...Damit wird kein Browser-Flow mehr gebraucht.
💡 Tipp: Mit
--ephemeralals zusätzlichem Flag wird das Gerät beim Disconnect automatisch wieder gelöscht. Perfekt für CI-Runner, Container, kurzlebige VMs.
Schritt 8: Magic DNS testen
Wenn magic_dns: true in der Config gesetzt ist, sind eure Geräte per Hostname erreichbar:
ping marius-laptopWenn das nicht klappt:
sudo tailscale up --accept-dnsoder explizit setzen:
sudo headscale nodes rename --identifier 1 marius-laptopSchritt 9: ACLs konfigurieren
Standardmäßig dürfen alle Tailscale-Geräte mit allen anderen reden. Mit ACLs kontrolliert ihr das.
sudo nano /etc/headscale/acl.yamlBeispiel — strikte Trennung zwischen Servern und Clients:
groups:
group:admins:
- marius
group:family:
- frau
- kind1
tagOwners:
tag:server:
- group:admins
tag:work:
- group:admins
acls:
# Admins dürfen alles
- action: accept
src:
- group:admins
dst:
- "*:*"
# Familie darf nur auf bestimmte Geräte (z.B. Plex-Server)
- action: accept
src:
- group:family
dst:
- tag:server:32400Headscale neu laden:
sudo systemctl reload headscaleACL-Test:
sudo headscale policy check⚠️ Wichtig: ACLs sind default-deny, sobald sie aktiv sind. Wenn ihr eine fehlerhafte ACL einspielt und niemand mehr verbinden kann, könnt ihr die Datei löschen oder leeren — dann fällt Headscale auf „alle dürfen alles“ zurück.
Schritt 10: Subnet-Routing — komplettes LAN freigeben
Ein Tailscale-Node kann als Gateway für ein ganzes Subnetz fungieren — perfekt für Homelab-Zugriff.
Auf dem Gateway-Node (z.B. ein Raspberry Pi im LAN):
sudo tailscale up --login-server=https://headscale.example.com \
--advertise-routes=192.168.1.0/24Auf dem Headscale-Server die Route freischalten:
sudo headscale nodes list
sudo headscale routes list
sudo headscale routes enable -r ROUTE_IDAndere Tailscale-Clients akzeptieren die Route mit:
sudo tailscale up --accept-routesJetzt erreicht jeder Tailscale-Client euer komplettes LAN — ohne Portfreigaben.
Backups
Headscale speichert minimal Daten, aber kritisch:
/etc/headscale/config.yaml— Konfiguration/etc/headscale/acl.yaml— ACLs/var/lib/headscale/db.sqlite— User, Nodes, Pre-Auth-Keys/var/lib/headscale/noise_private.key— Krypto-Schlüssel
Backup-Script
sudo nano /usr/local/bin/headscale-backup.shInhalt:
#!/bin/bash
set -e
BACKUP_DIR="/var/backups/headscale"
TIMESTAMP=$(date +%Y%m%d-%H%M%S)
RETENTION_DAYS=14
mkdir -p "$BACKUP_DIR"
tar -czf "$BACKUP_DIR/headscale-$TIMESTAMP.tar.gz" \
/etc/headscale \
/var/lib/headscale 2>/dev/null
find "$BACKUP_DIR" -type f -mtime +$RETENTION_DAYS -delete
echo "Backup completed: $TIMESTAMP"sudo chmod +x /usr/local/bin/headscale-backup.shMit systemd-Timer automatisieren — siehe restic-Tutorial.
⚠️ Best Practice: Wenn die
noise_private.keyweg ist, müssen sich alle Clients neu registrieren. Sichert sie verschlüsselt an einem zweiten Ort.
Updates
HEADSCALE_VERSION=$(curl -s https://api.github.com/repos/juanfont/headscale/releases/latest | grep tag_name | cut -d '"' -f 4)
cd /tmp
wget "https://github.com/juanfont/headscale/releases/download/${HEADSCALE_VERSION}/headscale_${HEADSCALE_VERSION#v}_linux_amd64.deb"
# Backup vor dem Update!
sudo /usr/local/bin/headscale-backup.sh
sudo dpkg -i "headscale_${HEADSCALE_VERSION#v}_linux_amd64.deb"
sudo systemctl restart headscale💡 Empfehlung: Headscale ist Pre-1.0 (aktuell ~0.23.x) und Releases können Breaking Changes enthalten. Lest die Release-Notes, bevor ihr updatet.
Zusammenfassung & Checkliste
| Schritt | Status |
|---|---|
| Debian 13 + öffentliche IP + Domain | ☐ |
| Nginx als Reverse Proxy installiert | ☐ |
Headscale .deb installiert | ☐ |
config.yaml angepasst | ☐ |
| Headscale-Service läuft | ☐ |
| Nginx Reverse Proxy + SSL eingerichtet | ☐ |
/health Endpoint via HTTPS erreichbar | ☐ |
| Erster User angelegt | ☐ |
| Erster Client erfolgreich verbunden | ☐ |
| Pre-Auth-Key generiert | ☐ |
| Magic DNS funktioniert | ☐ |
| ACLs definiert (optional) | ☐ |
| Subnet-Routing eingerichtet (optional) | ☐ |
| Backup-Script eingerichtet | ☐ |
Troubleshooting
Client kann sich nicht verbinden
- Headscale-URL korrekt (mit
https://)? curl https://headscale.example.com/healthvon außen erreichbar?- SSL-Zertifikat valide?
openssl s_client -connect headscale.example.com:443
„node has no IP addresses“
Adressbereich-Konflikt mit dem Client-Netz oder vergessen, in der Config einen Bereich zu setzen. prefixes-Section in config.yaml prüfen.
Magic DNS funktioniert nicht
- Auf Client:
sudo tailscale up --accept-dns - In
config.yaml:magic_dns: trueundbase_domaingesetzt? - Lokales DNS-Override aktiv? (
override_local_dns: true)
„Failed to register node“
- Pre-Auth-Key abgelaufen?
sudo headscale preauthkeys list --user marius - Key bereits verwendet und nicht
reusable?
NAT-Traversal scheitert (P2P-Verbindung kommt nicht)
- UDP-Port 3478 (STUN) offen?
- Mindestens ein Client hat keine direkte Internetverbindung? Dann läuft Traffic über DERP-Relay (langsamer, aber funktioniert)
- Eigenen DERP-Server hosten — siehe
derp.server.enabled: true
Performance schlecht
- Verbindungen laufen über DERP statt P2P?
tailscale netcheckauf Client zeigt es - Headscale-Server in Region weit weg? CDN-DERP nutzen oder eigenen DERP hosten
Nächste Schritte
- Eigenen DERP-Server hosten — Für noch mehr Privacy
- OIDC mit Authentik — User-Login statt Pre-Auth-Keys
- Web-UI über
headplane— Schöne Verwaltungsoberfläche - Subnet-Router HA — Zwei Gateways im LAN für Failover
- Exit-Node — Internet-Traffic über vertrauenswürdiges Gerät routen
Habt ihr Fragen oder Probleme? Schreibt es in die Kommentare — ich helfe gerne!