Zurück zum Blog

„Verbunden“ und trotzdem tot: die Jagd nach einem stillen WireGuard-Bug

„Verbunden“ und trotzdem tot: die Jagd nach einem stillen WireGuard-Bug

Datum 18. Juli 2026
Kategorie Netzwerk & Debugging
Lesezeit 9 Minuten

Interne Tools ins VPN statt ins Internet

Monitoring-Werkzeuge wie Prometheus, Alertmanager und cAdvisor haben eine Weboberfläche, die man gelegentlich braucht – aber nichts, was ins offene Internet gehört. Der klassische Kompromiss ist ein Reverse-Proxy mit Login davor. Es geht aber grundsätzlicher: Man macht sie gar nicht öffentlich, sondern nur über das Mesh-VPN (hier Tailscale/Headscale) erreichbar. Wer nicht im VPN ist, sieht nicht einmal einen geschlossenen Port – der Dienst existiert für ihn schlicht nicht.

Technisch bindet man die bislang nur lokal (127.0.0.1) lauschenden Dienste zusätzlich an die VPN-Adresse des Servers. Als bequemen Einstieg legt man im Identity-Provider (Authentik) drei Kachel-Verknüpfungen an, sichtbar nur für Administratoren. Die Kacheln zeigen auf den VPN-internen Namen des Servers (via MagicDNS). Zugang bekommt also nur, wer im VPN und in der Admin-Gruppe ist.

Der befreiende Nebeneffekt: kein Zertifikat nötig

Warum hier schlichtes HTTP genügt: Der Reflex „interner Dienst braucht trotzdem HTTPS“ sitzt tief. Aber ein Mesh-VPN auf WireGuard-Basis verschlüsselt den gesamten Verkehr bereits Ende-zu-Ende zwischen den Geräten. Ein zusätzliches TLS-Zertifikat auf einem VPN-internen Namen brächte keinen Vertraulichkeitsgewinn – dafür aber das leidige Problem, an so einen Namen überhaupt ein gültiges Zertifikat zu bekommen (die übliche Domain-Validierung funktioniert dort nicht). Über das VPN also einfach HTTP. Das löst ein Henne-Ei-Problem, indem es die Henne streicht.

Das Symptom: einmal ging es, dann nichts mehr

Kurz nach der Einrichtung funktionierte eine der Seiten – dann keine der drei mehr. „VPN ist verbunden“, meldete das Gerät. Trotzdem: jede der drei Adressen lief in einen Timeout. Ein Fehler, der einmal geht und dann dauerhaft nicht mehr, ist ein besonders undankbarer: Er schließt die simplen Erklärungen („falsch konfiguriert“) aus und deutet auf etwas Dynamisches.

Der Server war kerngesund

Erster Schritt: die Serverseite. Alle drei Container liefen, die Ports waren an die VPN-Adresse gebunden, und – entscheidend – lokal auf dem Server waren alle drei Oberflächen einwandfrei erreichbar. Die Bindung stand, der Dienst antwortete. Das Problem lag also nicht im Dienst, sondern irgendwo auf dem Weg zwischen Client und Server.

Eine Messfalle: Der lokale Test auf dem Server läuft über die Loopback- Schnittstelle – ein anderer Pfad als der eines entfernten Geräts, das über das VPN hereinkommt. „Auf dem Server geht es“ beweist, dass der Dienst lebt, aber nicht, dass er von außen erreichbar ist. Diese beiden Aussagen sauber zu trennen, hat später die entscheidende Zeit gespart.

Die Tücke: Ping funktioniert, TCP nicht

Der nächste Test schien zu beruhigen: Ein VPN-Ping vom Server zum Client kam prompt zurück – sogar über eine direkte Verbindung, kein Umweg über ein Relay, rund 25 Millisekunden. Die Geräte „sehen“ sich also. Und doch: Jeder Verbindungsversuch auf einen TCP-Port lief in den Timeout.

Warum der Ping trügt: Der Verbindungs-Ping eines Mesh-VPN nutzt ein eigenes Steuerprotokoll (Discovery), das prüft, ob zwei Knoten einen Pfad zueinander finden. Das ist die Steuerungsebene. Der eigentliche Nutzverkehr – deine TCP-Pakete – läuft über die Datenebene, den verschlüsselten WireGuard-Tunnel. Beide können unabhängig voneinander kaputtgehen. „Ping geht“ beweist die Steuerungsebene, über die Datenebene sagt es nichts. Genau diese Lücke war hier das Problem.

Der entscheidende Hinweis: rx 0

Die Auflösung stand in der Statusanzeige des VPN-Clients. Zum Server hieß es dort sinngemäß: Verbindung aktiv, direkt, gesendet: einige Kilobyte – empfangen: null. Der Client feuerte also Pakete in den Tunnel, bekam aber nichts zurück.

Das Muster merken: „Verbindung aktiv“ plus tx > 0 plus rx 0 ist die Signatur einer toten Datenebene bei intakter Steuerungsebene. Die Pakete gehen raus, die Gegenseite sendet keine Antwort (oder ihre Antworten kommen nicht an). In Kombination mit „Ping ok, aber jeder Port timeout“ ist die Diagnose damit praktisch sicher.

Der Gegenbeweis, der die Firewall entlastete

Bevor man an der richtigen Stelle repariert, muss man die falschen Verdächtigen ausschließen. Der naheliegende Verdacht war die Kette aus Container-Netzwerk, Port-Weiterleitung und Firewall-Regeln – ein berüchtigt komplexes Zusammenspiel, zumal ein Dienst am Rand eines fremden Docker-Netzes hing, dessen Isolationsregeln durchaus Verkehr verwerfen können.

Statt sich darin zu verlieren, half ein sauberer Gegenbeweis: ein Wegwerf-Proxy auf einem völlig anderen Port, der den Docker- und Firewall-Pfad komplett umgeht und stumpf auf die lokale Schnittstelle weiterreicht. Auch dieser Testport lief vom Client in den Timeout. Damit war klar: Es liegt nicht an Docker, nicht an der Port-Weiterleitung, nicht an der Netz-Isolation und nicht am Reverse-Proxy. Wenn selbst der einfachste denkbare Pfad scheitert, ist die Ursache tiefer – im Transport selbst.

Debugging-Technik: Wenn mehrere Schichten als Verdächtige in Frage kommen, baue einen Testfall, der so viele davon wie möglich überspringt. Scheitert er genauso, hast du alle übersprungenen Schichten mit einem Schlag entlastet – und die Suche auf die verbleibende eingegrenzt.

Ursache und Ein-Zeilen-Fix

Die verbleibende Schicht war der VPN-Dienst selbst. Sein Hintergrundprozess hielt nach einem Tag mit sehr viel Container-Aktivität veraltete WireGuard-Schlüssel. Die Steuerungsebene (Discovery, Ping) funktionierte damit weiter – deshalb der trügerische direkte Pong. Die Datenebene aber, die auf den aktuellen Schlüsseln beruht, verwarf die Pakete stumm. Ergebnis: gesendet, aber nie beantwortet. Genau das rx 0.

Der Fix ist eine einzige Zeile – den VPN-Hintergrunddienst auf dem Server neu starten, damit er frische Schlüssel zieht:

systemctl restart tailscaled

Sekunden später zeigte die Client-Statusanzeige empfangenen Verkehr, und alle drei Seiten luden – in rund einer zwanzigstel Sekunde. Keine Konfigurationsänderung, kein Umbau: Die Einrichtung war die ganze Zeit korrekt, nur der Transport hatte geklemmt.

Wiederkehr einplanen: Dieses Schlüssel-Veralten kann nach viel Container-Aktivität oder einem Neustart erneut auftreten und trifft dann jeden VPN-Dienst gleichzeitig. Das Merkmal ist immer dasselbe: Ping ok, aber TCP-Timeout und rx 0. Wer das Muster einmal kennt, spart sich beim nächsten Mal die halbe Fehlersuche und startet direkt den VPN-Dienst neu.

Lessons Learned

Die wichtigsten Erkenntnisse:
  • Interne Werkzeuge müssen nicht ins Internet – ein Mesh-VPN macht sie erreichbar, ohne Angriffsfläche zu schaffen.
  • Über ein WireGuard-Mesh genügt HTTP: Der Tunnel verschlüsselt bereits, ein zusätzliches Zertifikat auf einem VPN-internen Namen bringt nichts und ist mühsam zu beschaffen.
  • „Verbunden“ und „Ping geht“ beweisen die Steuerungsebene, nicht die Datenebene. Beide können getrennt ausfallen.
  • Das Muster aktiv, tx > 0, rx 0 ist die Signatur einer toten Datenebene – gesendet, nichts zurück.
  • „Auf dem Server geht es“ ist ein Loopback-Test und beweist nicht die Erreichbarkeit von außen. Die beiden Aussagen sauber trennen.
  • Bei vielen verdächtigen Schichten einen Testfall bauen, der möglichst viele überspringt. Scheitert er auch, sind sie alle entlastet.
  • Veraltete VPN-Schlüssel äußern sich als „alles verbunden, nichts geht“. Ein Neustart des VPN-Dienstes zieht frische Schlüssel – das Wissen um das Muster ist der eigentliche Fix.

Die verlässlichste Netzwerk-Diagnose beginnt nicht mit einer Vermutung, sondern mit der Frage, welche Ebene man gerade wirklich getestet hat. Ein Pong ist ein schöner Anblick – aber er beantwortet nur die halbe Frage.