„Verbunden“ und trotzdem tot: die Jagd nach einem stillen WireGuard-Bug
„Verbunden“ und trotzdem tot: die Jagd nach einem stillen WireGuard-Bug
Inhaltsverzeichnis
- Interne Tools ins VPN statt ins Internet
- Der befreiende Nebeneffekt: kein Zertifikat nötig
- Das Symptom: einmal ging es, dann nichts mehr
- Der Server war kerngesund
- Die Tücke: Ping funktioniert, TCP nicht
- Der entscheidende Hinweis:
rx 0 - Der Gegenbeweis, der die Firewall entlastete
- Ursache und Ein-Zeilen-Fix
- Lessons Learned
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
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.
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.
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.
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.
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.
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
- 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 0ist 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.