11 GB Restore über OpenVPN. Geschätzte Restzeit: 45 Minuten. Bei 3 MB/s. Im Jahr 2026.
Der Proxmox Backup Server steht am Remote-Standort, verbunden über eine pfSense mit WireGuard Site-to-Site. Aber der Proxmox-Hypervisor selbst hängt noch an einem OpenVPN-Tunnel. Single-Threaded. Userspace. MSS 1208 Bytes. Das ist kein Tuning-Problem — das ist ein architektonisches Limit.
Das Setup
Hypervisor (Hetzner) pfSense Gateway Remote-Standort
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ srv-r2d2 │──OpenVPN────►│ gw-obiwan │──WG─────►│ gw-yoda │
│ Proxmox VE │ tun0 │ pfSense 2.8 │ tun_wg0 │ pfSense │
│ │ 3 MB/s 😩 │ :51820 │ │ 172.20.x.x │
└──────────────┘ └──────────────┘ └──────────────┘
│
▼
PBS (172.20.20.4)
Der pfSense-Gateway hat bereits einen funktionierenden WireGuard Site-to-Site-Tunnel zum Remote-Standort. Nur der Hypervisor hängt noch am OpenVPN-Tropf.
Ziel: Hypervisor als neuen WireGuard-Peer auf die pfSense bringen. OpenVPN ablösen.
Die Migration in 5 Schritten
Schritt 1: WireGuard auf dem Hypervisor
apt-get install -y wireguard-tools
mkdir -p /etc/wireguard && chmod 700 /etc/wireguard
wg genkey | tee /etc/wireguard/private.key | wg pubkey > /etc/wireguard/public.key
chmod 600 /etc/wireguard/private.key
cat /etc/wireguard/public.key # Notieren!
Config erstellen:
# /etc/wireguard/wg0.conf
[Interface]
PrivateKey = <dein-private-key>
Address = 10.69.69.3/32
[Peer]
PublicKey = <pfsense-public-key>
Endpoint = <pfsense-wan-ip>:51820
AllowedIPs = 172.20.0.0/16, 10.69.69.0/24
PersistentKeepalive = 25
Schritt 2: Peer auf pfSense hinzufügen
Am einfachsten über die Web-UI: VPN → WireGuard → Peers → Add.
- Tunnel: tun_wg0 (der bestehende)
- Public Key: (vom Hypervisor)
- Allowed IPs: 10.69.69.3/32
- Persistent Keepalive: 25
Save. Apply.
Über SSH geht es auch — aber da lauert Pitfall #1.
Schritt 3: Switchover
# OpenVPN stoppen
systemctl stop openvpn-client@<dein-tunnel>
# WICHTIG: tun0 manuell entfernen (siehe Pitfall #3)
ip link del tun0 2>/dev/null
# WireGuard hoch
wg-quick up wg0
Schritt 4: Testen
ping -c2 10.69.69.1 # pfSense WireGuard-IP
ping -c2 172.20.20.4 # PBS am Remote-Standort
wg show # Handshake + Transfer prüfen
Schritt 5: Persistent machen
systemctl disable openvpn-client@<dein-tunnel>
systemctl enable wg-quick@wg0
Die drei Pitfalls
Klingt einfach? Ist es auch — bis es das nicht mehr ist.
Pitfall 1: filter_configure() auf pfSense killt alles
Wenn du den Peer per SSH und PHP in die config.xml schreibst, brauchst du danach einen Reload. Der intuitive Weg:
// NICHT MACHEN:
interface_reconfigure("opt1");
filter_configure(); // ← Das hier zerstört dein VPN
filter_configure() regeneriert alle pf-Regeln. Inklusive Anti-Spoofing. Wenn du gleichzeitig das Interface-Subnetz geändert hast (z.B. /30 auf /24), schreibt pfSense neue Anti-Spoofing-Regeln — und deine aktive VPN-Session fliegt raus. SSH-Timeout. Web-UI weg. Alles weg.
Der sichere Weg: Nur WireGuard synchen, keine Firewall anfassen:
require_once("/usr/local/pkg/wireguard/includes/wg.inc");
parse_config(true);
wg_tunnel_sync(array("tun_wg0"), true, true, false);
Das syncht nur die WireGuard-Config, ohne die Firewall-Regeln neu zu schreiben. Der bestehende Site-to-Site-Tunnel bleibt stehen.
Oder einfach die Web-UI nehmen. Ernsthaft. Bei pfSense-Config-Änderungen über SSH ist die Frage nicht ob du dich aussperrst, sondern wann.
Pitfall 2: Deine Tunnel-IP muss ins /30 passen
Auf meiner pfSense war das WireGuard-Interface als 10.69.69.1/30 konfiguriert. Der bestehende Site-to-Site-Peer hat 10.69.69.2. Ich wollte dem Hypervisor 10.69.69.5 geben.
Problem: /30 = nur 4 Adressen (.0 bis .3). Die .5 liegt außerhalb. pfSense hat keine Route zurück zum Hypervisor, weil die Adresse nicht im direkt verbundenen Subnetz liegt.
Mein erster Instinkt: Subnetz auf /24 erweitern. Das funktioniert — löst aber filter_configure() aus (Pitfall #1). Und damit war das Netzwerk tot.
Der einfache Fix: 10.69.69.3 nehmen statt .5. Die .3 liegt innerhalb des /30. Keine Subnetz-Änderung nötig. Kein filter_configure(). Kein Risiko.
/30 Subnetz:
.0 = Netzwerk
.1 = pfSense (Gateway)
.2 = Site-to-Site Peer
.3 = Hypervisor ← hier rein!
Pitfall 3: OpenVPN lässt tun0 stehen
systemctl stop openvpn-client@gw-tunnel
Du denkst, der Tunnel ist weg? Überraschung:
$ ip route | grep tun0
172.20.0.0/16 via 12.1.0.1 dev tun0 # ← Zombie-Route
172.22.22.0/24 via 12.1.0.1 dev tun0 # ← Noch eine
$ ip link show tun0
235: tun0: <POINTOPOINT,MULTICAST,NOARP,UP,LOWER_UP> # ← Interface lebt!
OpenVPN entfernt das tun-Interface nicht immer sauber beim Stoppen. Die alten Routen bleiben stehen. Wenn du jetzt wg-quick up wg0 machst:
RTNETLINK answers: File exists
[#] ip link delete dev wg0
wg-quick versucht die gleichen Routen zu setzen, scheitert am Konflikt, und räumt sein eigenes Interface wieder ab. Kein Tunnel.
Noch fieser: Wenn tun0 eine Route für 172.20.0.0/16 hat und wg0 auch eine setzt, gewinnt die spezifischere oder zuerst gesetzte Route. Dein Traffic geht weiter über das tote OpenVPN-Interface ins Nirvana.
Fix:
systemctl stop openvpn-client@<tunnel>
ip link del tun0 2>/dev/null # Interface + alle Routen entfernen
wg-quick up wg0 # Jetzt sauber
Bonus-Pitfall: </peers> in config.xml ist nicht eindeutig
Falls du den pfSense-Peer per Script in die config.xml schreibst und str_replace("</peers>", ...) verwendest: Vergiss es. Mehrere Packages nutzen <peers>-Tags — bei mir war es Squid Reverse Proxy. Mein neuer WireGuard-Peer landete in drei verschiedenen Sections.
Immer eindeutige Marker verwenden (z.B. den Public Key des benachbarten Peers) und Treffer-Anzahl validieren:
$count = substr_count($xml, $marker);
if ($count !== 1) { echo "ABORT"; exit(1); }
AllowedIPs: Was rein muss und was nicht
Eine Sache die mich 10 Minuten gekostet hat: Der Hypervisor hostet selbst das 10.0.0.0/24 Netz (VM-Bridge). Wenn du das in die WireGuard AllowedIPs packst:
# FALSCH:
AllowedIPs = 10.0.0.0/24, 172.20.0.0/16, 10.69.69.0/24
# RICHTIG:
AllowedIPs = 172.20.0.0/16, 10.69.69.0/24
Die 10.0.0.0/24 Route über WireGuard kollidiert mit der bestehenden Bridge-Route. wg-quick bricht ab. Logisch, wenn man drüber nachdenkt — aber in der Hitze des Gefechts packt man gerne einfach alle Netze rein die der alte OpenVPN-Tunnel geroutet hat.
Health-Check
Nicht vergessen: Der alte OpenVPN-Healthcheck-Cron startet sonst OpenVPN wieder:
# Alten Cron entfernen
rm /etc/cron.d/openvpn-healthcheck
# Neuen anlegen
cat > /etc/cron.d/wireguard-healthcheck << 'EOF'
*/5 * * * * root /usr/local/bin/wireguard-healthcheck.sh
EOF
cat > /usr/local/bin/wireguard-healthcheck.sh << 'SCRIPT'
#!/bin/bash
if ! ip link show wg0 >/dev/null 2>&1; then
logger -t "wireguard-healthcheck" "wg0 missing - restart"
systemctl restart wg-quick@wg0
sleep 3
fi
if ! ping -c 1 -W 5 10.69.69.1 >/dev/null 2>&1; then
logger -t "wireguard-healthcheck" "tunnel unreachable - restart"
systemctl restart wg-quick@wg0
fi
SCRIPT
chmod +x /usr/local/bin/wireguard-healthcheck.sh
Ergebnis
| Metrik | OpenVPN | WireGuard |
|---|---|---|
| Implementierung | Userspace, single-threaded | Kernel-Space |
| MSS | 1208 Bytes | 1420 Bytes |
| Throughput (PBS) | ~3 MB/s | Deutlich mehr |
| 11 GB Restore | ~45 Min (Timeout!) | Erfolgreich |
| CPU-Last | Spürbar (Crypto in Userspace) | Minimal |
Der Restore der vorher in Timeouts gelaufen ist, ging jetzt sauber durch.
Fazit
Die Migration selbst ist simpel: Keypair generieren, Config schreiben, Peer hinzufügen, umschalten. 15 Minuten Arbeit.
Die Pitfalls sind es, die dich erwischen:
filter_configure()nicht per CLI aufrufen — nurwg_tunnel_sync()- Tunnel-IP innerhalb des bestehenden Subnetzes wählen — spart die Subnetz-Änderung
- tun0 manuell entfernen nach OpenVPN-Stop —
ip link del tun0 - Nur eigene Netze in AllowedIPs — nicht das lokale VM-Netz
- Eindeutige Marker in config.xml —
</peers>ist mehrdeutig
Wenn du Proxmox PBS über einen Remote-Standort betreibst und noch OpenVPN nutzt: Der Wechsel auf WireGuard ist ein No-Brainer. Aber mach vorher ein Backup deiner pfSense-Config. Vertrau mir.