Tailscale Alternative einrichten Teil 1 / 4

Dein eigenes VPN: Headscale auf einem eigenen Server einrichten

Worum es geht

Tailscale ist ein hervorragendes Mesh-VPN, aber es setzt voraus, dass du Tailscale Inc. als Anbieter für die Verwaltung deiner Geräte (den sogenannten "Control-Server") vertraust. Headscale ist eine quelloffene Neuimplementierung genau dieses Control-Servers, kompatibel mit den offiziellen Tailscale-Apps auf all deinen Geräten, nur eben komplett selbst gehostet.

Diese Anleitung zeigt dir, wie du:

  1. einen eigenen Headscale-Server auf einem günstigen VPS aufsetzt
  2. einen eigenen DERP-Relay betreibst (das Fallback-Netzwerk für Verbindungen, die kein direktes Peer-to-Peer schaffen, z.B. hinter restriktivem NAT), damit du wirklich unabhängig von Tailscales Infrastruktur bist
  3. Familie oder Freunden Zugriff auf deine selbst gehosteten Dienste gibst
  4. deine eigenen Geräte und die anderer Personen sauber verwaltest

Wichtig: Diese Anleitung geht von einer Installation auf einem VPS mit Debian 13 aus. Mit anderen Distributionen oder Versionen können einzelne Befehle abweichen.

Voraussetzungen: Du solltest ein Terminal bedienen können, per SSH auf deinem Server eingeloggt sein und dort einen eigenen Benutzer mit sudo-Rechten haben. Falls du noch keinen passenden Server hast, zeigt dir der optionale Bonus-Teil Server-Einrichtung, wie du bei Hetzner einen Debian-13-VPS aufsetzt. Ein Sysadmin musst du für all das nicht sein.

Was du außerdem brauchst: eine eigene Domain, bei der du DNS-Einträge setzen kannst.


Teil 1: Headscale und einen eigenen DERP-Relay installieren

Alles läuft bei uns über Docker, das hält den Server aufgeräumt und macht Updates einfach. Als Reverse Proxy nutzen wir Caddy, der sich automatisch um Let's-Encrypt-Zertifikate kümmert.

Docker installieren

Terminal
sudo apt install -y ca-certificates curl gnupg
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/debian/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg

echo \
  "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/debian \
  $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
  sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
sudo usermod -aG docker $USER

Kurz aus- und wieder einloggen, damit die Docker-Gruppenmitgliedschaft greift.

Caddy installieren

Terminal
sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https curl
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list
sudo apt update
sudo apt install -y caddy

Verzeichnisstruktur anlegen

Terminal
mkdir -p ~/headscale/headscale/config
mkdir -p ~/headscale/headscale/data
mkdir -p ~/headscale/derper/certs
cd ~/headscale

Domains, die wir brauchen

Für den Rest der Anleitung gehen wir von folgendem Domain-Schema aus (alles unter deiner eigenen Domain, ersetze example.com entsprechend):

Domain Zweck
vpn.example.com Headscale-Control-Server (öffentlich)
derp.example.com Eigener DERP-Relay (öffentlich, nur der Port zählt)
internal.example.com MagicDNS-Basisdomain (nur intern, nie öffentlich nutzen, siehe Warnkasten unten)

⚠️ Wichtig: Die MagicDNS-Basisdomain (internal.example.com) darf niemals für andere, öffentlich erreichbare Dienste verwendet werden. Headscale "übernimmt" für Geräte in deinem Tailnet jede Anfrage unter dieser Domain und beantwortet sie selbst (mit "nicht gefunden", falls kein passendes Gerät existiert) — unabhängig davon, was im echten, öffentlichen DNS steht. Öffentliche Dienste (siehe Teil 4) gehören auf eine andere Subdomain oder direkt auf deine Hauptdomain.

Docker-Compose-Stack

~/headscale/docker-compose.yml
# ~/headscale/docker-compose.yml
networks:
  vpn:
    driver: bridge

services:
  headscale:
    image: headscale/headscale:0.29.2
    container_name: headscale
    restart: unless-stopped
    command: serve
    volumes:
      - ./headscale/config:/etc/headscale
      - ./headscale/data:/var/lib/headscale
    ports:
      - "127.0.0.1:8080:8080"
      - "127.0.0.1:9090:9090"
    networks:
      - vpn
    mem_limit: 256m

  derper:
    build: ./derper
    container_name: derper
    restart: unless-stopped
    command: >
      --hostname=derp.example.com
      --certmode=manual
      --certdir=/app/certs
      -a=:8443
      --stun-port=3478
      --verify-clients=false
    volumes:
      - ./derper/certs:/app/certs
    ports:
      - "8443:8443"
      - "3478:3478/udp"
    networks:
      - vpn
    mem_limit: 128m

Wichtig: Immer eine konkrete Versionsnummer angeben (hier 0.29.2), niemals :latest. Headscale ändert zwischen Minor-Versionen gelegentlich die Config-Struktur, mit einem festen Tag behältst du die Kontrolle darüber, wann du updatest.

DERP-Relay bauen

Der offizielle Tailscale-DERP-Server (derper) wird aus dem Quellcode gebaut:

~/headscale/derper/Dockerfile
# ~/headscale/derper/Dockerfile
FROM golang:1.22-alpine AS build
RUN apk add --no-cache git ca-certificates
ENV GOTOOLCHAIN=auto
RUN go install tailscale.com/cmd/derper@latest

FROM alpine:3.20
RUN apk add --no-cache ca-certificates
COPY --from=build /go/bin/derper /usr/local/bin/derper
ENTRYPOINT ["/usr/local/bin/derper"]

GOTOOLCHAIN=auto sorgt dafür, dass Go sich bei Bedarf selbst eine passende Compiler-Version nachlädt, falls das Basis-Image zu alt ist.

Headscale-Konfiguration

~/headscale/headscale/config/config.yaml
# ~/headscale/headscale/config/config.yaml
server_url: https://vpn.example.com
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

trusted_proxies: []

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

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

derp:
  server:
    enabled: false
  urls: []
  paths:
    - /etc/headscale/derp.yaml
  auto_update_enabled: false
  update_frequency: 3h

disable_check_updates: false

node:
  expiry: 0
  ephemeral:
    inactivity_timeout: 30m
  routes:
    ha:
      probe_interval: 10s
      probe_timeout: 5s

database:
  type: sqlite
  sqlite:
    path: /var/lib/headscale/db.sqlite
    write_ahead_log: true

tls_letsencrypt_hostname: ""
tls_cert_path: ""
tls_key_path: ""

log:
  level: info
  format: text

policy:
  mode: file
  path: ""

dns:
  magic_dns: true
  base_domain: internal.example.com
  override_local_dns: true
  nameservers:
    global:
      - 1.1.1.1
      - 1.0.0.1
  search_domains: []
  extra_records: []

unix_socket: /var/run/headscale/headscale.sock
unix_socket_permission: "0770"

Eigene DERP-Map, damit Headscale unseren Relay statt Tailscales öffentliche Server nutzt:

~/headscale/headscale/config/derp.yaml
# ~/headscale/headscale/config/derp.yaml
regions:
  900:
    regionid: 900
    regioncode: custom
    regionname: "Eigener DERP-Relay"
    nodes:
      - name: "900a"
        regionid: 900
        hostname: derp.example.com
        stunport: 3478
        stunonly: false
        derpport: 8443

Caddy konfigurieren

/etc/caddy/Caddyfile
# /etc/caddy/Caddyfile

vpn.example.com {
    reverse_proxy 127.0.0.1:8080
}

# Dient nur der Zertifikatsbeschaffung, der eigentliche DERP-Traffic
# läuft direkt auf Port 8443 an Caddy vorbei.
derp.example.com {
    respond 404
}
Terminal
sudo systemctl reload caddy

Alles starten

Terminal
cd ~/headscale
docker compose up -d

DERP-Zertifikat einrichten

Das ist der etwas kniffligste Teil: Let's-Encrypt-Zertifikate werden immer über Port 80/443 validiert, unabhängig davon, auf welchem Port dein Dienst tatsächlich läuft. Da Caddy diese Ports bereits belegt, holt Caddy das Zertifikat für derp.example.com (der respond 404-Block von eben dient genau dafür), und ein kleines Skript kopiert es regelmäßig zu derper:

Terminal
#!/bin/bash
# ~/headscale/derper/sync-cert.sh
set -euo pipefail

SRC="/var/lib/caddy/.local/share/caddy/certificates/acme-v02.api.letsencrypt.org-directory/derp.example.com"
DEST="/home/deinname/headscale/derper/certs"

cp "$SRC/derp.example.com.crt" "$DEST/derp.example.com.crt"
cp "$SRC/derp.example.com.key" "$DEST/derp.example.com.key"
chmod 644 "$DEST/derp.example.com.crt"
chmod 600 "$DEST/derp.example.com.key"

cd /home/deinname/headscale
docker compose up -d --force-recreate derper
Terminal
chmod +x ~/headscale/derper/sync-cert.sh
sudo ~/headscale/derper/sync-cert.sh

Damit das Zertifikat auch nach der automatischen Erneuerung durch Caddy (alle paar Wochen) übernommen wird, richten wir einen wöchentlichen Cronjob ein:

Terminal
sudo crontab -e

Zeile hinzufügen:

Terminal
0 3 * * 1 /home/deinname/headscale/derper/sync-cert.sh >> /var/log/derper-cert-sync.log 2>&1

Testen

Terminal
curl -I https://vpn.example.com/health
curl -I https://derp.example.com:8443

Beide sollten antworten. Damit steht dein eigener Headscale-Server samt DERP-Relay. Weiter geht's in Teil 2 mit dem Anlegen von Benutzern.


Wichtige Stolpersteine