Das Problem

Der Aufbau: pfSense als Gateway, WireGuard-Tunnel zu einem VPN-Provider als primärer Internet-Gateway, FritzBox als Fallback. Das ganze verwaltet über eine Gateway-Group – Tier 1 WireGuard, Tier 2 WAN.

In der Theorie: Wenn WireGuard ausfällt, wechselt die Gateway-Group auf WAN. Kein Ausfall, kein Problem.

In der Praxis: WireGuard reconnected, alle Clients sind offline. Minutenlang. Bis man WireGuard manuell neustartet.

pfSense-Web-UI zeigt dabei fröhlich:

WireguardVPN    10.5.0.2    2.1ms    0.0%    online

Online. Kein Paketverlust. Alles prima. Nur dass kein einziges Paket ankommt.


TL;DR

Auf pfSense pingt dpinger bei WireGuard-Gateways ohne explizit gesetztes Monitor-Target die eigene Interface-IP (10.5.0.2). Das Interface antwortet immer auf Pings – unabhängig davon ob der Tunnel zum Peer aktiv ist. Ergebnis: Gateway-Group erkennt den Tunnelausfall nie, failover findet nie statt, Traffic geht ins Nichts.

Fix: Monitor-Target auf eine IP setzen, die nur durch den aktiven Tunnel erreichbar ist (z.B. 10.5.0.1 als VPN-Provider-internes Gateway).


Diagnose

Der Blick auf die laufenden dpinger-Prozesse zeigt das Problem sofort:

ps aux | grep dpinger
/usr/local/bin/dpinger -i WireguardVPN -B 10.5.0.2 [...] 10.5.0.2

Die letzte Adresse ist das Ping-Ziel. Und das ist 10.5.0.2 – also die eigene Interface-IP. dpinger schickt ICMP-Pakete an sich selbst. Natürlich kommen die zurück.

Zum Vergleich der WAN-Gateway-Eintrag:

/usr/local/bin/dpinger -i WAN_PPPOE -B 192.168.1.1 [...] 1.1.1.1

Hier pingt dpinger 1.1.1.1 – eine externe IP, die wirklich nur erreichbar ist, wenn der WAN-Uplink funktioniert. Genau so soll es sein.


Ursache

pfSense setzt das Monitor-Target standardmäßig auf die Gateway-IP. Bei klassischen Gateways (WAN, VLAN) ist das eine IP beim ISP oder Router – also außerhalb. Bei WireGuard-Tunneln ist die Gateway-IP (10.5.0.2) aber die lokale Tunnel-Interface-Adresse. Die gehört dem eigenen System.

Resultat:

  1. WireGuard-Tunnel bricht ab (Handshake-Timeout, Server-Wechsel beim Provider, NAT-Session abgelaufen)
  2. dpinger pingt 10.5.0.2 → eigenes Interface antwortet → 0% Loss, 2ms Latenz
  3. Gateway-Group sieht: Tier 1 online → kein Failover
  4. Default Route bleibt auf tun_wg0
  5. Alle Clients senden Pakete in den toten Tunnel

Das ist kein Bug – es ist ein Konfigurationsproblem. Das Monitor-Target ist einfach falsch.


Lösung

Schritt 1: Richtiges Monitor-Target finden

Gesucht: Eine IP, die ausschließlich durch den aktiven WireGuard-Tunnel erreichbar ist.

VPN-Provider bieten intern meist ein Gateway an. Testen:

# Aus der pfSense-Shell (SSH)
ping -c 3 10.5.0.1

Antwortet 10.5.0.1 mit 9-10ms? Dann ist das das interne Gateway des VPN-Providers und ein perfektes Monitor-Target.

Alternativ: Eine beliebige IP, die nur durch den Tunnel routet, z.B. eine IP im VPN-internen Subnetz.

Schritt 2: Monitor-Target in pfSense setzen

Web-UI: System → Routing → Gateways → WireGuard-Gateway bearbeiten → Monitor IP → 10.5.0.1

Oder direkt in config.xml:

# Backup
cp /cf/conf/config.xml /cf/conf/config.xml.bak.$(date +%Y%m%d_%H%M%S)

# Relevanter Abschnitt in config.xml
grep -n "WireguardVPN\|monitor" /cf/conf/config.xml | head -20

Den <monitor>10.5.0.1</monitor> Tag im Gateway-Block setzen.

Schritt 3: dpinger neu starten

# Laufenden dpinger für diesen Gateway stoppen
PID=$(ps aux | grep "dpinger.*WireguardVPN" | grep -v grep | awk '{print $2}')
kill $PID

# pfSense startet dpinger automatisch neu, oder manuell:
/etc/rc.gateway_alarm  # nicht notwendig, dpinger startet selbst

Verifikation:

ps aux | grep dpinger | grep WireguardVPN
# Erwartete Ausgabe:
# /usr/local/bin/dpinger -i WireguardVPN -B 10.5.0.2 [...] 10.5.0.1
#                                                                ^^^^^^
#                                                         Jetzt 10.5.0.1!
# Gateway-Status prüfen
# Delay jetzt ~10ms statt 2ms = pingt durch den Tunnel, nicht sich selbst

Bonus-Fixes

Solange man schon dabei ist:

PersistentKeepalive zu niedrig oder zu hoch

Hinter NAT (z.B. FritzBox) läuft die NAT-Session für UDP ab – je nach Router nach 30-120 Sekunden ohne Traffic. Standard-Empfehlung für WireGuard hinter NAT:

PersistentKeepalive = 25

Wert von 115 ist zu hoch für viele NAT-Implementierungen.

gw_down_kill_states aktivieren

Wenn das Gateway als down erkannt wird, hängen bestehende TCP-States noch am alten Gateway. Mit gw_down_kill_states = down werden die States beim Gateway-Wechsel gekillt – Clients stellen sofort neue Verbindungen her.

Web-UI: System → Routing → Gateways → Gateway bearbeiten → “Flush States when Gateway goes down”


Verifikation

Der Beweis: WireGuard-Tunnel manuell beenden und beobachten.

# Tunnel kurz stoppen (ACHTUNG: kurze Unterbrechung!)
wg-quick down tun_wg0 2>/dev/null || ifconfig tun_wg0 down

# dpinger sollte jetzt Loss melden (nach ~20-30 Sekunden)
# Gateway-Status:

Wenn dpinger jetzt Loss > 20% meldet und der Gateway-Status auf “down” wechselt, funktioniert alles korrekt. Dann Tunnel wieder hochfahren und Gateway-Group wechselt zurück.


Lessons Learned

Für alle WireGuard-Gateways in pfSense gilt: Das Monitor-Target muss explizit gesetzt werden. Die Standardeinstellung (eigene Interface-IP) ist für WireGuard-Tunnel unbrauchbar.

Beim nächsten pfSense-Audit prüfen:

  • ps aux | grep dpinger – wohin pingt jeder Gateway wirklich?
  • Ist der Monitor-Target eine externe IP (gut) oder die eigene Interface-IP (schlecht)?

Das gilt übrigens auch für OpenVPN-Gateways in pfSense – gleiche Falle.