Published on

Headscale – Installation mit Docker und Caddy

Authors
  • avatar
    Name
    Tobias Steltner
    Twitter

Intro

In dieser Anleitung zeige ich, wie man Headscale mit Docker und Caddy installiert. Headscale ist eine selbstgehostete Open-Source-Implementierung des Tailscale-Kontrollservers. Tailscale ist ein benutzerfreundliches VPN, das auf WireGuard basiert und eine einfache und sichere Netzwerkverbindung zwischen Geräten ermöglicht, ohne komplexe Konfigurationen. Es nutzt eine Zero-Trust-Architektur, um den Netzwerkverkehr zu sichern und erleichtert den Zugriff auf Ressourcen, unabhängig vom Standort der Benutzer.

1. Grundvoraussetzung

  • Docker & Docker Compose v2 (Debian / Ubuntu)
  • Ein VPS mit fester IP und diese sollte per A-Record auf eine Domain zeigen (FQDN)
  • Port 80, 443, 443udp offen + SSH

2. Vorwort

Bevor wir loslegen, möchte ich kurz mein Setup erklären. Ich nutze es seit 4 Wochen in meinem Homelab und bin damit sehr zufrieden. Es ist wichtig zu wissen, dass Headscale sich noch in der Entwicklung befindet, aber es läuft bei mir sehr stabil.

Mein Ziel war es, bestimmte Dienste öffentlich zugänglich zu machen und gleichzeitig bestmöglich abzusichern. Dafür verwende ich ein Setup mit Traefik und CrowdSec. Für besonders sensible Anwendungen wie Vaultwarden suchte ich jedoch eine zusätzliche Sicherheitsmaßnahme. Ich wollte für diese Anwendungen SSL-Zertifikate haben, die nur für mich oder ausgewählte Personen zugänglich sind. Um das zu realisieren, habe ich Headscale eingerichtet.

Das heißt im Klartext: Mein Vaultwarden läuft komplett lokal bei mir zu Hause, hier ist kein Port geöffnet. Verbunden mit Headscale kann ich mir dann allerdings SSL-Zertifikate besorgen und mein Vaultwarden nur von einer bestimmten IP zugänglich machen.

Der Headscale-Server verbindet alle benötigten Server einfach und schnell per VPN. Über einen integrierten Caddy-Container kann ich ausgewählten Diensten SSL-Zertifikate zuweisen und den Zugriff über IP-Blockregeln steuern.

Um es noch komfortabler zu gestalten, habe ich einen sehr günstigen VPS (1 € pro Monat) mit 1 CPU und 1 GB RAM gemietet, was völlig ausreicht. Ich verwende eine Domain für öffentliche Dienste, die über Traefik läuft, und eine zweite Domain für meine internen Dienste. Diese nenne ich hier public.com und private.com.

Auf diese Weise könnte man generell Dienste nach außen freigeben, ohne Ports am Heimrouter öffnen zu müssen, aber das war nicht mein Hauptziel. Ich hoffe, meine Grundidee ist klar, und nun zur Installation.

headscale

3. Docker Setup

Hier kümmern wir uns erstmal darum das die Container sauber laufen.

3.1 Ordner anlegen

Ordner Struktur anlegen.

mkdir+cd
mkdir -p /opt/containers/ && cd /opt/containers

Git Repro clonen.

git.clone
git clone https://github.com/2TAP2B/headscale.git

Weitere Ordner erstellen

mkdir+cd
cd headscale
mkdir -p caddy/config caddy/data

3.2 Caddyfile anpassen

nano
nano /opt/containers/headscale/caddy/Caddyfile

Hier bitte in der zweiten Zeile headscale.private.com entsprechend anpassen.

3.3 Headscale config anpassen

nano
nano /opt/containers/headscale/headscale/config/config.yml

Die Server Url:

server_url: https://headscale.private.com

bitte anpassen.

Die Base Doamin:

base_domain: headscale.private.com

bitte anpassen.

3.4 Container starten

docker.start
docker compose -f /opt/containers/headscale/docker-compose.yml up -d

4. Einrichtung von Headscale

Öffnet nun:

https://headscale.private.com/web

Natürlich anpassen die Domain

Hier solltest du nun im WebUI von Headscale ankommen und ein API Fehler taucht auf.

headscale1

Wir gehen nochmal ins hauptverzeichnis rein.

cd
cd /opt/containers/headscale/

Jetzt erstellen wir uns unseren API-Token.

api.key
 docker compose exec headscale headscale apikeys create

Kopiere den API Token unter Settings in das Feld Headscale API Key und klicke einmal Test Server.

headscale2

Jetzt erstellen wir erstmal einen User. Unter dem Punkt User, Add User.

Jetzt kann man sich den Tailscale Client von der offiziell Tailscale Seite downloaden.

Tailscale Download

Für Linux zB.

curl
curl -fsSL https://tailscale.com/install.sh | sh

Die Client Software ist Open Source. Die Verbindung via Windows oder Linux ist sehr einfach und mit einem Befehl zu realiseren.

Öffnet diesen Link (entsprechend angepasst auf deine Domain)

https://headscale.private.com/windows

Dort steht nun:

headscale3

tailscale login –login-server https://headscale.private.com

Haut den Befehl in die Konsole bzw. Windows-Powershell und folgt den Anweisungen im Fenster.

headscale4
headscale5

Dann müsstet ihr den neuen Client per SSH auf dem Headscale-Server hinzufügen, oder ihr kopiert den “mkey:bblabla” und fügt diesen im Web-UI unter “Device View”, “New Device” ein, wählt einen Benutzer und schon seid ihr mit Headscale verbunden.

Auch der Server auf dem Headscale läuft muss manuell als Client registriert werden, wenn er Teil des Netzwerkes sein soll!


4.1 Der klassische VPN: IP verschleiern

Um jetzt nicht nur einen VPN zu haben, sondern auch ein Proxy der unseren Traffic durch die entsprechende IP Routet zu erstellen, müssen wir einen der Clients zur “Exit Node” machen. Das machen wir ganz einfach über die CLI beim gewünschten Client per

tailscale
tailscale set --advertise-exit-node

Jetzt sagen wir dem Betriebssystem noch das wir wirklich den Traffic weiterleiten wollen.

echo
echo 'net.ipv4.ip_forward = 1' > /etc/sysctl.d/99-vpn.conf
echo 'net.ipv6.conf.all.forwarding = 1' >> /etc/sysctl.d/99-vpn.conf
sysctl -p /etc/sysctl.d/99-vpn.conf

Zu guter letzt müssen wir im WebUI noch die angegebene Route freigeben. Wählt dafür das entsprechende Device aus und drückt auf “Pending.”

headscale6

5. DynDNS Setup (optional)

Wenn ihr wie ich einen ganz normalen Internetanschluss mit einer dynamischen IP habt und den Datenverkehr eures VPN über diese wechselnde IP leiten möchtet, habe ich eine einfache Lösung. Ich habe ein kleines Skript erstellt, das das Problem der wechselnden IP-Adressen behebt.

Das Bashscript sollte auch per git clone schon vorhanden sein.

script.sh
#!/bin/bash

DDNS_DOMAIN="public.com"
CADDYFILE="/opt/containers/headscale/caddy/Caddyfile"

# Erhalte die aktuelle IP-Adresse von der DDNS-Domain
CURRENT_IP=$(dig +short $DDNS_DOMAIN)

# Prüfe, ob die aktuelle IP-Adresse gültig ist
if [[ -z "$CURRENT_IP" ]]; then
    echo "Fehler: Konnte die IP-Adresse von $DDNS_DOMAIN nicht ermitteln."
    exit 1
fi

# Ersetze den Platzhalter im Caddyfile durch die aktuelle IP-Adresse
sed -i "s/CURRENT_IP_PLACEHOLDER/$CURRENT_IP/" $CADDYFILE


# Starte Caddy neu, um die Änderungen zu übernehmen
docker compose -f /opt/containers/headscale/docker-compose.yml restart caddy

# Füge den Platzhalter zurück, um zukünftige Ersetzungen zu ermöglichen
sed -i "s/$CURRENT_IP/CURRENT_IP_PLACEHOLDER/" $CADDYFILE

Ihr müsst nur bei DDNS_DOMAIN=”euredyndnsdomain.de” eintragen.

Dann machen wir es noch ausführbar.

chmod+x
chmod +x /opt/containers/headscale/ip.sh

Und erstellen einen Cronjob, damit es automatisch die IP aktualisiert.

crontab
crontab -e

Am Ende der Datei fügst du folgende Zeile ein:

crontab
0 4 * * * /opt/containers/headscale/ip.sh

5.1 Troubleshooting

Wenn ihr das Setup mit DynDNS und diesem Skript verwendet, müsst ihr Folgendes beachten: Nach einem Neustart eures Stacks solltet ihr das ip.sh-Skript erneut ausführen. Andernfalls wird der Reverse Proxy nicht richtig funktionieren.


6. Quellen:

https://github.com/juanfont/headscale

https://headscale.net

https://blog.gurucomputing.com.au/Smart%20VPNS%20with%20Headscale/Setting%20up%20Headscale