Drei Nächte in Folge meldet Zabbix no recent backup für einen VM-Host, der nachts über einen WireGuard-Hub sein vzdump nach Hause schickt. Du öffnest wg show auf dem Hub, erwartest einen toten Tunnel — und siehst einen Handshake, der vor Sekunden war. Alles grün. Nur: Es kommt nichts an.

TL;DR:

  • Ein frischer WireGuard-Handshake sagt nichts über IP-Konnektivität — nur, dass die Crypto-Schicht funktioniert
  • ifconfig <interface> ohne inet-Zeile ist der eigentliche Befund — nicht der Handshake, nicht die Logs
  • Nach dem Fix nicht einfach alle versäumten Backups auf einmal nachfahren, wenn der Hub physisch auf derselben Maschine hängt wie der Uplink — das sättigt die Leitung tagsüber

Das Problem

Setup: ein Proxmox-Host (pve-dagobah) schickt sein nächtliches vzdump über einen WireGuard-Site-to-Site-Tunnel an einen Hub (hub-obiwan), der auf einer Hetzner-Maschine als VM läuft. Seit drei Nächten läuft das Backup-Freshness-Monitoring auf Rot.

Erster Blick auf den Hub:

wg show
interface: tun_wg0
  public key: (hidden)
  private key: (hidden)
  listening port: 51820

peer: (hidden)
  endpoint: <peer-ip>:51820
  allowed ips: 10.77.77.0/30, 192.168.66.0/24
  latest handshake: 42 seconds ago
  transfer: 0 B received, 0 B sent

Handshake vor 42 Sekunden. Sieht gesund aus. Aber: null Transfer in drei Nächten ist kein vzdump-Transfer — das sind GiB-große Jobs. Und verwirrend dazu: Ein bereits bestehendes LAN-zu-LAN-Forwarding über denselben Tunnel läuft in dem Moment problemlos weiter. Wäre der Tunnel wirklich tot, dürfte da auch nichts durchkommen. Ist er aber nicht — jedenfalls nicht komplett.

Die Diagnose

Ping-Test vom Hub auf die Gegenseite:

ping -c 5 192.168.66.10
PING 192.168.66.10 (192.168.66.10): 56 data bytes

--- 192.168.66.10 ping statistics ---
5 packets transmitted, 0 packets received, 100.0% packet loss

100 % Loss — in beide Richtungen, auch für selbst generierte Pakete. Gleichzeitig zeigt das pfSense-Gateway-Monitoring (dpinger) seinen eigenen Zielhost ebenfalls als nicht erreichbar, obwohl der Handshake frisch bleibt.

Der Befehl, der die Sache aufklärt, ist nicht wg show — es ist der banalste Netzwerk-Check überhaupt:

ifconfig tun_wg0

Bad (so sah es aus):

tun_wg0: flags=8051<UP,POINTOPOINT,RUNNING,MULTICAST> mtu 1420
        groups: tun wg

Keine inet-Zeile. Keine IP auf dem Interface. Punkt.

Good (nach dem Fix):

tun_wg0: flags=8051<UP,POINTOPOINT,RUNNING,MULTICAST> mtu 1420
        inet 10.77.77.1 --> 10.77.77.2 netmask 0xfffffffc
        groups: tun wg

(Linux-Äquivalent für den gleichen Check auf einem Linux-Hub: ip -4 addr show tun_wg0 — selbe Frage, andere Syntax.)

Eine Ablenkung auf dem Weg dahin: In den Logs lief parallel ein pvestatd-Fingerprint-Fehler zu einem angebundenen Backup-Storage auf. Sah nach Zusammenhang aus, war keiner — reine Koinzidenz, kostete trotzdem Zeit, bis klar war, dass beide Fehler nichts miteinander zu tun haben.

Exkurs: Auf die Konsole kommen, wenn das SSH-Passwort nirgends steht

Bevor überhaupt ifconfig auf dem Hub laufen konnte, musste erstmal draufgekommen werden — das pfSense-SSH-Passwort für hub-obiwan war nirgends dokumentiert (Credential-Store negativ geprüft). Zugang geht trotzdem, weil der Hub als VM auf pve-dagobah läuft:

# Auf pve-dagobah: VGA-Framebuffer der VM als PPM abziehen
printf "screendump /tmp/gw.ppm\n" | qm monitor <vmid>

Das PPM dann nach PNG konvertieren und von einem vision-fähigen Modell transkribieren lassen, Befehle Zeichen für Zeichen per sendkey eintippen. Drei Fallen dabei:

  • Keine Großbuchstaben als Key-Namen: sendkey E wird still verworfen. grep -E also als egrep tippen, oder den Großbuchstaben explizit als sendkey shift-e senden.
  • Sonderzeichen brauchen eigene Namen: | = sendkey shift-backslash, _ = sendkey shift-minus, Enter = sendkey ret, Space = sendkey spc.
  • Vor dem Tippen zweimal screendumpen und per md5 vergleichen — sonst tippst du blind in eine Session, in der gerade jemand anderes sitzt.

Kein serielles Device vorhanden (qm terminal 108 scheitert mit „unable to find a serial interface"), kein QEMU-Guest-Agent installiert — Screendump + sendkey war der einzige Weg rein.

Die Ursache

Das Perfide: Ein WireGuard-Handshake läuft komplett auf der Crypto-Schicht (UDP-Pakete, Diffie-Hellman, Key-Rotation) — völlig unabhängig davon, ob das Tunnel-Interface überhaupt eine IP-Adresse trägt. wg show beantwortet nur die Frage „ist die Verschlüsselung gesund?" — nicht „kann hier IP-Verkehr fließen?". Beides sauber getrennt zu sehen, braucht zwei verschiedene Tools.

Ohne inet-Adresse auf tun_wg0 fehlt die Connected-Route fürs Tunnel-Subnetz (10.77.77.0/30). Die Folge trifft drei Dinge gleichzeitig, aber unterschiedlich sichtbar:

  1. dpinger-Pings (Gateway-Monitoring) kommen beim Hub an, werden aber nie beantwortet — es gibt keinen Rückweg, weil die Quelladresse für die Antwort fehlt.
  2. Lokal generierte Pings verlassen das Interface gar nicht erst — ohne eigene Adresse hat der Stack keine Quelle, die er einsetzen könnte.
  3. Bereits geroutetes LAN-zu-LAN-Forwarding läuft weiter, weil WireGuard Pakete anhand der Peer-AllowedIPs und bestehender Routing-Table-Einträge weiterleitet — das braucht keine eigene Interface-Adresse, solange die Route für das Ziel-Subnetz schon vorher existierte.

Genau Punkt 3 ist der Teil, der am längsten in die Irre führt: Der Tunnel sieht „halb lebendig" aus, weil ein Teil des Traffics tatsächlich durchkommt.

Die Lösung

Sofort-Fix zur Laufzeit:

ifconfig tun_wg0 inet 10.77.77.1/30 alias

Dauerhaft gehört die Adresse unters Interface, nicht unter die WireGuard-Tunnel-Konfiguration selbst — ein Muster, das auch in pfSense WireGuard Client-VPN: Drei Pitfalls die deinen Tunnel killen schon auftauchte: Interfaces → dein zugewiesenes WireGuard-Interface → IPv4 Configuration Type: Static IPv4 → 10.77.77.1/30. Nicht unter VPN → WireGuard → Tunnels — dort steht nur die Krypto-Config, die pfSense bei zugewiesenen Interfaces schlicht ignoriert.

Beweis, dass es sitzt, direkt in der Config:

grep -n "ipaddr" /cf/conf/config.xml | grep "10.77.77.1"
<ipaddr>10.77.77.1</ipaddr>

Danach lief die nächste Nacht sauber durch.

Drei Nächte Backup-Rückstand wollen nachgeholt werden — und genau hier liegt die zweite Falle. hub-obiwan läuft als VM physisch auf pve-dagobah. Heißt: Traffic von pve-dagobah zum eigenen Hub verlässt die Maschine über den Hetzner-Uplink nach draußen, trifft den WireGuard-Endpoint, und kommt über denselben Uplink wieder rein. Jedes Byte Backup-Daten kostet den Uplink doppelt — einmal raus, einmal rein — plus zstd-CPU auf dem erzeugenden Host.

Ein Tagsüber-Catch-up-Lauf (unter anderem ein ~340-GiB-Job für cloud-leia) sättigte die Leitung so weit, dass extern erreichbare Dienste hinter dem NAT von hub-obiwan instabil wurden. Mitten am Tag, nicht nachts, wo es niemand gemerkt hätte.

Fix: Nachhol-Läufe ausschließlich ins bestehende Nachtfenster legen (23:50–08:00), nicht tagsüber reinpressen, egal wie sehr die Zabbix-Alarme drängen.

Und wenn du einen laufenden Nachhol-Job stoppen musst:

pkill -f "[v]zdump"

Nicht pkill -f vzdump — das matcht auch die eigene SSH-Kommandozeile (die den String vzdump enthält) und killt sich selbst, statt den Zielprozess zu treffen. Der eckige-Klammer-Trick ([v]zdump statt vzdump) entschärft genau das. Gleicher Grund, warum du nie tail auf eine Datei namens vzdump-*.log im selben pgrep/pkill-Aufruf laufen lässt — der Dateiname matcht sich selbst als Prozess.

Und der eigentliche Haken: Ein hart abgebrochener vzdump-Lauf hinterlässt am Gast gern ein lock: backup. Danach prüfen und aufräumen — sonst endet der nächste Lauf mit job errors, und du hast dir genau den stillen Backup-Ausfall gebaut, der im vzdump-Stale-Lock-Artikel zehn Tage unbemerkt blieb:

pct status <vmid>            # bei QEMU: qm status <vmid>
# steht dort "lock: backup" → freigeben
pct unlock <vmid>            # bei QEMU: qm unlock <vmid>

Dazu ein zweiter, belegter Punkt: Manuelles vzdump bei einem ZFS-Pool über ~80 % Belegung hat auf demselben Hypervisor schon alle Gäste eingefroren. Vor einem Tagsüber-Nachhol-Lauf also erst die Pool-Belegung prüfen — und im Zweifel auf das Nachtfenster warten.

Lessons Learned

  • Ein Handshake ist kein Beweis für Konnektivität. WireGuard trennt Crypto-Layer und IP-Layer vollständig. wg show grün heißt nur „Schlüssel okay" — für die IP-Frage brauchst du ifconfig/ip a zusätzlich, immer.
  • Teilweise funktionierender Traffic ist der gefährlichste Zustand beim Debuggen. Dass bestehende LAN-Routen weiterliefen, kostete mehr Zeit als ein komplett totes Interface gekostet hätte — ein sauberer Totalausfall wäre schneller gefunden gewesen.
  • Parallele, unabhängige Fehler sind keine Korrelation. Der pvestatd-Fingerprint-Fehler lief zufällig gleichzeitig — ohne Beweis für einen kausalen Zusammenhang nicht weiterverfolgen, auch wenn die Zeitnähe verlockend danach aussieht.
  • Physische Topologie schlägt logische Topologie. Ein Hub, der als VM auf derselben Maschine läuft, die ihn auch per Backup befüllen soll, bedeutet Hairpin-Traffic — jedes Byte zweimal über den Uplink. Nachholen von Backup-Rückständen braucht dieselbe Zeitfenster-Disziplin wie der reguläre Lauf.
  • Credential-Lücken auf kritischen Hosts sind ein eigenes Risiko. Kein dokumentiertes SSH-Passwort für einen Hub, der Backups trägt, bedeutet im Ernstfall Screendump+sendkey statt eines normalen Logins — funktioniert, kostet aber Zeit, die man beim nächsten Incident nicht hat.

Checkliste

  • wg show zeigt frischen Handshake, aber nur minimalen Transfer → sofort ifconfig/ip a auf dem Interface prüfen, nicht weiter in WireGuard-Logs suchen
  • Fehlt die inet-Zeile → Adresse unter Interfaces (nicht unter VPN→WireGuard→Tunnels) als Static IPv4 setzen
  • Nach dem Fix: Beweis in config.xml ziehen (grep ipaddr), nicht nur auf die GUI vertrauen
  • dpinger-Gateway-Status auf dem betroffenen Tunnel separat prüfen — „online" kann trotzdem komplett tot sein
  • Vor dem Nachholen versäumter Backups: Topologie checken — läuft der Zielhub physisch auf derselben Uplink-Maschine, die die Backup-Daten sendet?
  • Nachhol-Jobs ins bestehende Nachtfenster legen, nicht tagsüber durchdrücken, auch wenn Zabbix drängt
  • Zum Stoppen: pkill -f "[v]zdump" mit Bracket-Trick, nie blank pkill -f vzdump
  • Nach dem Abbruch pct status/qm status auf lock: backup geprüft und ggf. unlock — sonst stiller Backup-Ausfall beim nächsten Lauf
  • Vor Tagsüber-Nachhol-Läufen die ZFS-Pool-Belegung geprüft (über ~80 % → Freeze-Risiko)
  • Für kritische Hosts ohne dokumentierte SSH-Credentials: Alternativzugang (z. B. Hypervisor-Konsole) vorher testen, nicht erst im Incident