Eintrag

Selektives VPN-Routing: Bestimmte IPs über ProtonVPN tunneln

Selektives VPN-Routing: Bestimmte IPs über ProtonVPN tunneln

Du betreibst ein WireGuard-VPN mit wg-easy und willst, dass bestimmter Traffic deiner Clients nicht über deine Heim-IP rausgeht, sondern anonym über einen kommerziellen VPN-Provider? Genau dafür gibt es Policy-Based Routing (PBR). Damit routest du gezielt einzelne IPs oder Domains durch einen zweiten WireGuard-Tunnel - zum Beispiel ProtonVPN - während der Rest normal weiterläuft.

Falls du WireGuard und wg-easy noch nicht kennst: Im WireGuard-Grundlagen-Post findest du alles zum Einstieg, und im v15-Migrations-Post die Neuerungen der aktuellen Version.


Warum selektives Routing?

Stell dir vor: Deine Familie und Freunde nutzen dein WireGuard-VPN, um auf Plex, Jellyfin und Overseerr zuzugreifen. Für 99% des Traffics ist das perfekt - die Pakete fließen direkt von deinem Homelab zum Ziel.

Aber es gibt Situationen, in denen du nicht willst, dass die Ziel-Website deine echte WAN-IP sieht. Beispiele:

  • Tracker-Foren mit Registrierung
  • Download-Seiten, die IPs loggen
  • Services, bei denen du deine Privatsphäre schützen willst

Die naive Lösung wäre ein Full Tunnel über ProtonVPN. Problem: Aller Traffic deiner VPN-Clients würde dann durch den kommerziellen VPN laufen - langsamer, unnötig, und Plex-Streams würden über die Schweiz geroutet statt direkt.

Policy-Based Routing löst das elegant: Du definierst eine Liste von IPs und Domains. Nur Traffic zu diesen Zielen geht durch den ProtonVPN-Tunnel. Alles andere fließt normal.

flowchart TB
    Client([VPN-Client]) --> WG[wg-easy Server<br>wg0 Interface]

    WG --> Check{Ziel-IP in<br>Route-Liste?}
    Check -->|Nein| Direct[Direktes Routing<br>über Heim-IP]
    Check -->|Ja| Proton[ProtonVPN-Tunnel<br>proton0 Interface]

    Direct --> Target1[Plex, Jellyfin<br>Homelab-Services]
    Direct --> Target2[Normales Internet<br>deine WAN-IP]
    Proton --> Target3[Tracker-Forum<br>sieht ProtonVPN-IP]

    style Client fill:#e1f5ff
    style Proton fill:#ffe1e1
    style Direct fill:#e1ffe1
    style Check fill:#fff3e0

Wie funktioniert PBR?

Policy-Based Routing klingt kompliziert, ist aber im Kern ein einfacher Dreischritt:

  1. Markieren: Pakete, deren Ziel-IP in einer Liste steht, bekommen ein “Etikett” (fwmark)
  2. Routen: Pakete mit diesem Etikett werden über eine separate Routing-Tabelle geleitet
  3. Tunneln: Die separate Routing-Tabelle zeigt auf das ProtonVPN-Interface

Die Komponenten im Überblick

Komponente Tool Aufgabe
IP-Liste ipset Hält alle Ziel-IPs in einer performanten Hash-Tabelle
Paket-Markierung iptables mangle Markiert Pakete mit fwmark wenn Ziel im ipset
Routing-Regel ip rule Leitet markierte Pakete an Tabelle 100
Routing-Tabelle ip route table 100 Default-Route über proton0
NAT iptables nat MASQUERADE auf dem proton0 Interface
VPN-Tunnel wg-quick WireGuard-Tunnel zu ProtonVPN

Paketfluss im Detail

So läuft ein Paket durch den Stack, wenn ein VPN-Client eine IP aus der Route-Liste aufruft:

flowchart LR
    A[VPN-Client<br>10.8.0.3] -->|Paket an<br>178.17.x.x| B[wg0]
    B -->|mangle<br>PREROUTING| C{ipset<br>vpn-routes}
    C -->|Match!| D[fwmark 0x1]
    D -->|ip rule| E[Table 100]
    E -->|default dev<br>proton0| F[MASQUERADE]
    F --> G[proton0<br>Tunnel]
    G --> H[ProtonVPN<br>Server CH]
    H --> I[Ziel-Website<br>sieht CH-IP]

    style C fill:#fff3e0
    style D fill:#ffe1e1
    style G fill:#e1f5ff

Und für normalen Traffic (Ziel-IP nicht in der Liste):

flowchart LR
    A[VPN-Client<br>10.8.0.3] -->|Paket an<br>192.168.60.x| B[wg0]
    B -->|mangle<br>PREROUTING| C{ipset<br>vpn-routes}
    C -->|Kein Match| D[Kein fwmark]
    D -->|Main Table| E[eth0<br>normales Routing]
    E --> F[Homelab<br>Service]

    style C fill:#fff3e0
    style D fill:#e1ffe1

Der entscheidende Punkt: Das ipset-Lookup in der mangle-Chain ist extrem schnell (O(1) dank Hash-Tabelle). Selbst mit hunderten Einträgen merkst du keine Latenz.


Voraussetzungen

Bevor du loslegst, brauchst du:

  • wg-easy läuft bereits (v14 oder v15, egal)
  • ProtonVPN-Account (kostenlos reicht, aber Paid hat mehr Server und Port-Forwarding)
  • Root-Zugang auf der wg-easy VM
  • Pakete: wireguard-tools, ipset, dnsutils, iptables, cron

Pakete installieren

1
2
3
sudo apt update
sudo apt install -y wireguard-tools ipset dnsutils iptables cron
sudo systemctl enable cron

ProtonVPN WireGuard-Config holen

  1. Öffne ProtonVPN → Downloads → WireGuard
  2. Wähle einen Server (z.B. Schweiz, Niederlande, Island)
  3. NAT-PMP (Port-Weiterleitung): An (falls du P2P nutzt)
  4. Klicke auf “Config generieren” und lade die .conf herunter

Die Config sieht ungefähr so aus:

1
2
3
4
5
6
7
8
9
10
11
[Interface]
PrivateKey = abc123...
Address = 10.2.0.2/32
DNS = 10.2.0.1

[Peer]
# CH#937
PublicKey = xyz789...
AllowedIPs = 0.0.0.0/0, ::/0
Endpoint = 62.169.136.3:51820
PersistentKeepalive = 25

Schritt 1: Verzeichnis und Config anlegen

1
sudo mkdir -p /opt/wg-easy/proton-pbr

Kopiere die ProtonVPN-Config und passe sie an:

1
2
3
4
5
6
7
8
9
10
11
12
[Interface]
PrivateKey = abc123...
Address = 10.2.0.2/32
# DNS = 10.2.0.1
Table = off

[Peer]
# CH#937
PublicKey = xyz789...
AllowedIPs = 0.0.0.0/0, ::/0
Endpoint = 62.169.136.3:51820
PersistentKeepalive = 25

Zwei wichtige Änderungen:

Änderung Warum
DNS auskommentiert Wir wollen den DNS-Server des Homelabs nutzen (z.B. Pi-hole), nicht den von ProtonVPN. Ohne diese Änderung würde wg-quick die /etc/resolv.conf überschreiben.
Table = off hinzugefügt Verhindert, dass wg-quick automatisch Routing-Regeln anlegt. Ohne Table = off würde aller Traffic durch den Tunnel gehen - das Gegenteil von dem, was wir wollen. Das Routing übernimmt stattdessen unser PBR-Setup mit fwmark und ipset.
1
2
# Config-Permissions einschränken (enthält Private Key!)
sudo chmod 600 /opt/wg-easy/proton-pbr/proton0.conf

Der Private Key in proton0.conf ist sensibel. Stelle sicher, dass nur root die Datei lesen kann.


Schritt 2: Route-Liste erstellen

Die routes.conf definiert, welche IPs und Domains durch den Tunnel gehen sollen:

1
2
3
4
5
6
7
8
9
10
11
12
13
# PBR Routes - IPs und Domains die über ProtonVPN geroutet werden
# Eine IP oder Domain pro Zeile
# Leerzeilen und Kommentare (#) werden ignoriert
# Nach Änderungen: pbr-update.sh ausführen oder auf nächsten Cron warten

# === IPs ===
178.17.166.194
185.195.236.11
103.214.7.28

# === Domains (werden per dig aufgelöst) ===
# example-tracker.org
# some-forum.net

Format-Regeln:

Typ Beispiel Verhalten
Kommentar # Das ist ein Kommentar Wird ignoriert
Leerzeile   Wird übersprungen
IPv4-Adresse 178.17.166.194 Wird direkt ins ipset übernommen
Domain example-tracker.org Wird per dig aufgelöst, alle A-Records werden ins ipset übernommen

Warum Domains unterstützt werden: Manche Websites wechseln ihre IPs (CDN, Load Balancer). Mit Domain-Einträgen löst das Update-Script die IPs alle 15 Minuten neu auf. Wenn sich die IP ändert, wird das ipset automatisch aktualisiert.

Trage hier nur Ziele ein, die wirklich über ProtonVPN laufen sollen. Je weniger Einträge, desto geringer die Komplexität.


Schritt 3: Setup-Script

Das Setup-Script wird beim Systemstart ausgeführt und richtet den kompletten PBR-Stack ein:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
#!/bin/bash
# pbr-setup.sh - Richtet ProtonVPN PBR-Tunnel ein
# Wird beim Systemstart ausgeführt (@reboot cron)
set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
CONF="$SCRIPT_DIR/proton0.conf"
ROUTES="$SCRIPT_DIR/routes.conf"
IPSET_NAME="vpn-routes"
TABLE=100
MARK=0x1
IFACE="proton0"
WG_IFACE="wg0"

log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*"; }

# Config-Dateien prüfen
if [[ ! -f "$CONF" ]]; then
    log "FEHLER: $CONF nicht gefunden"
    exit 1
fi

if [[ ! -f "$ROUTES" ]]; then
    log "FEHLER: $ROUTES nicht gefunden"
    exit 1
fi

# --- 1. ProtonVPN Tunnel starten ---
if ip link show "$IFACE" &>/dev/null; then
    log "Tunnel $IFACE läuft bereits, wird neu gestartet..."
    wg-quick down "$CONF" 2>/dev/null || true
fi
log "Starte ProtonVPN Tunnel..."
wg-quick up "$CONF"
sleep 2

if ! ip link show "$IFACE" &>/dev/null; then
    log "FEHLER: $IFACE nicht gestartet"
    exit 1
fi
log "Tunnel $IFACE aktiv"

# --- 2. ipset anlegen ---
if ipset list "$IPSET_NAME" &>/dev/null; then
    ipset flush "$IPSET_NAME"
else
    ipset create "$IPSET_NAME" hash:ip
fi

# IPs und Domains aus routes.conf laden
while IFS= read -r line; do
    line=$(echo "$line" | sed 's/#.*//' | xargs)
    [[ -z "$line" ]] && continue

    if [[ "$line" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
        ipset add "$IPSET_NAME" "$line" -exist
        log "IP hinzugefügt: $line"
    else
        ips=$(dig +short "$line" A 2>/dev/null | grep -E '^[0-9]+\.' || true)
        if [[ -n "$ips" ]]; then
            while IFS= read -r ip; do
                ipset add "$IPSET_NAME" "$ip" -exist
                log "Domain $line -> $ip"
            done <<< "$ips"
        else
            log "WARNUNG: Domain $line konnte nicht aufgelöst werden"
        fi
    fi
done < "$ROUTES"

# --- 3. Routing-Tabelle 100 ---
ip route replace default dev "$IFACE" table "$TABLE"
log "Routing-Tabelle $TABLE: default via $IFACE"

# --- 4. IP Rule: fwmark → Tabelle 100 ---
if ! ip rule show | grep -q "fwmark $MARK.*lookup $TABLE"; then
    ip rule add fwmark "$MARK" table "$TABLE" priority 100
    log "IP Rule: fwmark $MARK -> table $TABLE"
fi

# --- 5. iptables mangle: Pakete markieren ---
if ! iptables -t mangle -C PREROUTING -i "$WG_IFACE" \
     -m set --match-set "$IPSET_NAME" dst \
     -j MARK --set-mark "$MARK" 2>/dev/null; then
    iptables -t mangle -A PREROUTING -i "$WG_IFACE" \
             -m set --match-set "$IPSET_NAME" dst \
             -j MARK --set-mark "$MARK"
    log "Mangle PREROUTING Regel gesetzt"
fi

# --- 6. NAT/MASQUERADE ---
if ! iptables -t nat -C POSTROUTING -o "$IFACE" -j MASQUERADE 2>/dev/null; then
    iptables -t nat -A POSTROUTING -o "$IFACE" -j MASQUERADE
    log "MASQUERADE für $IFACE gesetzt"
fi

# --- 7. FORWARD-Regeln ---
# Markierten Traffic durchlassen
if ! iptables -C FORWARD -i "$WG_IFACE" \
     -m set --match-set "$IPSET_NAME" dst -j ACCEPT 2>/dev/null; then
    # Vor einer eventuellen DROP-Regel einfügen
    DROP_POS=$(iptables -L FORWARD --line-numbers -n 2>/dev/null \
               | awk '/^[0-9].*DROP/{print $1; exit}')
    if [[ -n "$DROP_POS" ]]; then
        iptables -I FORWARD "$DROP_POS" -i "$WG_IFACE" \
                 -m set --match-set "$IPSET_NAME" dst -j ACCEPT
    else
        iptables -A FORWARD -i "$WG_IFACE" \
                 -m set --match-set "$IPSET_NAME" dst -j ACCEPT
    fi
    log "FORWARD ACCEPT für vpn-routes gesetzt"
fi

# Antwort-Traffic zurück zum Client
if ! iptables -C FORWARD -i "$IFACE" -o "$WG_IFACE" \
     -m state --state RELATED,ESTABLISHED -j ACCEPT 2>/dev/null; then
    iptables -I FORWARD 1 -i "$IFACE" -o "$WG_IFACE" \
             -m state --state RELATED,ESTABLISHED -j ACCEPT
    log "FORWARD RELATED/ESTABLISHED gesetzt"
fi

log "PBR-Setup abgeschlossen"
log "Verifizierung: curl --interface $IFACE https://ifconfig.me"

Was das Script Schritt für Schritt macht:

Schritt Befehl Erklärung
1 wg-quick up Startet den WireGuard-Tunnel zu ProtonVPN
2 ipset create Erstellt eine Hash-Tabelle für die Ziel-IPs
3 ip route table 100 Eigene Routing-Tabelle die alles über proton0 schickt
4 ip rule fwmark Pakete mit Mark 0x1 nutzen Tabelle 100 statt Main
5 iptables mangle Markiert Pakete von wg0 deren Ziel im ipset ist
6 iptables nat MASQUERADE: Quell-IP wird zu ProtonVPN-Tunnel-IP
7 iptables FORWARD Erlaubt den markierten Traffic und die Antworten

Idempotenz: Jeder Schritt prüft vorher, ob die Regel bereits existiert. Das Script kann gefahrlos mehrfach ausgeführt werden - es dupliziert keine Regeln.

Die FORWARD-Regeln müssen vor einer eventuellen DROP-Regel stehen. Das Script erkennt automatisch die Position einer existierenden DROP-Regel und fügt die ACCEPT-Regel davor ein.


Schritt 4: Update-Script

Das Update-Script hält die IP-Liste aktuell - besonders wichtig für Domain-Einträge, deren IPs sich ändern können:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
#!/bin/bash
# pbr-update.sh - Aktualisiert ipset mit IPs/Domains aus routes.conf
# Cron: */15 * * * *
set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
ROUTES="$SCRIPT_DIR/routes.conf"
IPSET_NAME="vpn-routes"

log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*"; }

if [[ ! -f "$ROUTES" ]]; then
    log "FEHLER: $ROUTES nicht gefunden"
    exit 1
fi

if ! ipset list "$IPSET_NAME" &>/dev/null; then
    log "FEHLER: ipset $IPSET_NAME existiert nicht. pbr-setup.sh zuerst ausführen."
    exit 1
fi

# Soll-Zustand aus routes.conf berechnen
declare -A WANTED_IPS

while IFS= read -r line; do
    line=$(echo "$line" | sed 's/#.*//' | xargs)
    [[ -z "$line" ]] && continue

    if [[ "$line" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
        WANTED_IPS["$line"]=1
    else
        ips=$(dig +short "$line" A 2>/dev/null | grep -E '^[0-9]+\.' || true)
        if [[ -n "$ips" ]]; then
            while IFS= read -r ip; do
                WANTED_IPS["$ip"]=1
                log "Domain $line -> $ip"
            done <<< "$ips"
        fi
    fi
done < "$ROUTES"

# Neue IPs hinzufügen
for ip in "${!WANTED_IPS[@]}"; do
    ipset add "$IPSET_NAME" "$ip" -exist
done

# Stale IPs entfernen (im ipset aber nicht mehr in routes.conf)
CURRENT_IPS=$(ipset list "$IPSET_NAME" -output plain 2>/dev/null \
              | grep -E '^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$' || true)
removed=0
if [[ -n "$CURRENT_IPS" ]]; then
    while IFS= read -r ip; do
        if [[ -z "${WANTED_IPS[$ip]+x}" ]]; then
            ipset del "$IPSET_NAME" "$ip" 2>/dev/null || true
            log "Stale IP entfernt: $ip"
            ((removed++))
        fi
    done <<< "$CURRENT_IPS"
fi

log "Update: ${#WANTED_IPS[@]} IPs aktiv, $removed entfernt"

Was passiert hier?

  1. Soll-Zustand berechnen: Alle IPs und aufgelösten Domains aus routes.conf sammeln
  2. Hinzufügen: Neue IPs ins ipset eintragen (existierende werden ignoriert dank -exist)
  3. Bereinigen: IPs die nicht mehr in routes.conf stehen, aus dem ipset entfernen

Das Bereinigen ist wichtig: Wenn du eine IP oder Domain aus routes.conf entfernst, wird sie beim nächsten Cron-Lauf automatisch aus dem ipset gelöscht. Ohne diesen Schritt würde die alte IP ewig im ipset bleiben.


Schritt 5: Aktivieren

1
2
3
4
5
6
# Scripts ausführbar machen
sudo chmod +x /opt/wg-easy/proton-pbr/pbr-setup.sh
sudo chmod +x /opt/wg-easy/proton-pbr/pbr-update.sh

# PBR jetzt starten
sudo /opt/wg-easy/proton-pbr/pbr-setup.sh

Erwartete Ausgabe:

1
2
3
4
5
6
7
8
9
10
11
12
[2026-02-03 20:15:00] Starte ProtonVPN Tunnel...
[2026-02-03 20:15:02] Tunnel proton0 aktiv
[2026-02-03 20:15:02] IP hinzugefügt: 178.17.166.194
[2026-02-03 20:15:02] IP hinzugefügt: 185.195.236.11
[2026-02-03 20:15:02] IP hinzugefügt: 103.214.7.28
[2026-02-03 20:15:02] Routing-Tabelle 100: default via proton0
[2026-02-03 20:15:02] IP Rule: fwmark 0x1 -> table 100
[2026-02-03 20:15:02] Mangle PREROUTING Regel gesetzt
[2026-02-03 20:15:02] MASQUERADE für proton0 gesetzt
[2026-02-03 20:15:02] FORWARD ACCEPT für vpn-routes gesetzt
[2026-02-03 20:15:02] FORWARD RELATED/ESTABLISHED gesetzt
[2026-02-03 20:15:02] PBR-Setup abgeschlossen

Tunnel verifizieren

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# Ist der Tunnel aktiv?
sudo wg show proton0

# Welche Exit-IP hat der Tunnel?
sudo curl --interface proton0 https://ifconfig.me
# Erwartung: Eine ProtonVPN-IP (NICHT deine WAN-IP)

# Welche IPs sind im ipset?
sudo ipset list vpn-routes

# Ist die Routing-Tabelle korrekt?
sudo ip route show table 100
# Erwartung: default dev proton0 scope link

# Ist die IP Rule gesetzt?
sudo ip rule show | grep fwmark
# Erwartung: 100: from all fwmark 0x1 lookup 100

Schritt 6: Autostart per Cron

Damit das PBR nach einem Reboot automatisch startet:

1
sudo crontab -e

Füge diese zwei Zeilen ein:

1
2
@reboot sleep 30 && /opt/wg-easy/proton-pbr/pbr-setup.sh >> /var/log/pbr-setup.log 2>&1
*/15 * * * * /opt/wg-easy/proton-pbr/pbr-update.sh >> /var/log/pbr-update.log 2>&1
Eintrag Wann Was
@reboot sleep 30 30 Sekunden nach Boot PBR-Stack komplett einrichten. Das sleep 30 gibt Docker und wg-easy Zeit zum Starten.
*/15 * * * * Alle 15 Minuten IP-Liste aktualisieren (Domain-Auflösung, Stale-IPs entfernen)

Die Logs landen in /var/log/pbr-setup.log und /var/log/pbr-update.log. Praktisch fürs Debugging.


IPs und Domains verwalten

Neuen Eintrag hinzufügen

1
2
3
4
5
6
7
8
# IP hinzufügen
echo "203.0.113.42" | sudo tee -a /opt/wg-easy/proton-pbr/routes.conf

# Domain hinzufügen
echo "some-tracker-forum.net" | sudo tee -a /opt/wg-easy/proton-pbr/routes.conf

# Sofort anwenden (statt auf Cron zu warten)
sudo /opt/wg-easy/proton-pbr/pbr-update.sh

Eintrag entfernen

Lösche die Zeile aus routes.conf, dann:

1
sudo /opt/wg-easy/proton-pbr/pbr-update.sh

Das Update-Script erkennt, dass die IP nicht mehr im Soll-Zustand ist, und entfernt sie aus dem ipset.

Aktuelle Liste anzeigen

1
2
3
4
5
# Was steht in routes.conf?
cat /opt/wg-easy/proton-pbr/routes.conf

# Was ist aktuell im ipset aktiv?
sudo ipset list vpn-routes

Zusammenspiel mit wg-easy Hooks

PBR und die wg-easy Firewall-Hooks (PostUp/PostDown) ergänzen sich. Die Hooks steuern, welche Dienste ein Client erreichen darf. PBR steuert, über welchen Weg der Traffic zu bestimmten Zielen fließt.

Reihenfolge der Paketverarbeitung:

1
2
3
4
5
6
7
8
9
10
11
12
13
Paket von VPN-Client (wg0)
    │
    ├── 1. mangle PREROUTING: fwmark setzen (PBR)
    │
    ├── 2. FORWARD-Chain: Erlaubt? (wg-easy Hooks)
    │   ├── ACCEPT → weiter
    │   └── DROP → Paket verworfen
    │
    ├── 3. Routing-Entscheidung
    │   ├── fwmark 0x1 → Table 100 → proton0 (PBR)
    │   └── kein fwmark → Main Table → eth0 (normal)
    │
    └── 4. NAT POSTROUTING: MASQUERADE

Das bedeutet: Wenn ein Client per Whitelist-Hook nur Plex und Jellyfin erreichen darf, wird Traffic zu einer IP in routes.conf trotzdem erst durch die FORWARD-Chain geprüft. Ist die IP nicht in der Whitelist, wird das Paket verworfen - egal ob PBR es markiert hat.

PBR ändert nur den Weg, nicht die Berechtigung. Deine Firewall-Regeln greifen wie gewohnt.


Troubleshooting

Tunnel steht nicht

1
2
3
4
5
6
7
8
# Interface vorhanden?
ip link show proton0

# Falls nicht: manuell starten
sudo wg-quick up /opt/wg-easy/proton-pbr/proton0.conf

# Handshake prüfen (sollte < 2 Min sein)
sudo wg show proton0 | grep "latest handshake"

Kein Handshake? Die ProtonVPN-Config ist abgelaufen oder der Server nicht erreichbar. Generiere eine neue Config in der ProtonVPN-Web-UI.

Traffic geht nicht durch den Tunnel

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 1. Ist die IP im ipset?
sudo ipset test vpn-routes 178.17.166.194

# 2. Wird das Paket markiert?
sudo iptables -t mangle -L PREROUTING -n -v
# Zähler sollten bei Traffic hochgehen

# 3. Ist die Rule aktiv?
sudo ip rule show | grep fwmark

# 4. Geht Table 100 über proton0?
sudo ip route show table 100

# 5. MASQUERADE aktiv?
sudo iptables -t nat -L POSTROUTING -n -v

DNS-Probleme nach Setup

Wenn nach dem Start von pbr-setup.sh keine Domains mehr aufgelöst werden:

  • Prüfe ob proton0.conf tatsächlich # DNS = 10.2.0.1 (auskommentiert) hat
  • Ohne Auskommentieren überschreibt wg-quick die /etc/resolv.conf
1
2
cat /etc/resolv.conf
# Sollte deinen normalen DNS zeigen, NICHT 10.2.0.1

Alles zurücksetzen

Falls etwas komplett schiefgeht:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# Tunnel stoppen
sudo wg-quick down /opt/wg-easy/proton-pbr/proton0.conf

# iptables-Regeln entfernen
sudo iptables -t mangle -D PREROUTING -i wg0 -m set --match-set vpn-routes dst -j MARK --set-mark 0x1
sudo iptables -t nat -D POSTROUTING -o proton0 -j MASQUERADE
sudo iptables -D FORWARD -i wg0 -m set --match-set vpn-routes dst -j ACCEPT
sudo iptables -D FORWARD -i proton0 -o wg0 -m state --state RELATED,ESTABLISHED -j ACCEPT

# IP Rule und Routing-Tabelle entfernen
sudo ip rule del fwmark 0x1 table 100
sudo ip route flush table 100

# ipset löschen
sudo ipset destroy vpn-routes

# Crontab-Einträge entfernen
sudo crontab -e
# Die zwei PBR-Zeilen löschen

Danach ist alles wieder im Ausgangszustand. Dein wg-easy läuft normal weiter - nur ohne das selektive ProtonVPN-Routing.


Sicherheitshinweise

Ein paar Punkte, die du im Hinterkopf behalten solltest:

Private Keys: Die proton0.conf enthält deinen ProtonVPN Private Key. Die Datei sollte nur für root lesbar sein (chmod 600).

DNS-Leaks: Wenn du Table = off vergisst, geht aller Traffic (auch DNS) durch ProtonVPN. Wenn du DNS auskommentierst aber Table = off vergisst, hast du potenziell DNS-Leaks in die andere Richtung. Beides zusammen ist die richtige Konfiguration.

WebRTC-Leaks: PBR schützt auf Netzwerkebene. Browser-basierte Leaks (WebRTC) sind ein Client-seitiges Problem und werden durch PBR nicht adressiert.

Kill Switch: Es gibt keinen automatischen Kill Switch. Wenn der ProtonVPN-Tunnel abbricht, werden markierte Pakete trotzdem über Table 100 geroutet - aber da kein Interface mehr da ist, werden sie verworfen. Der Traffic “fällt” also nicht auf die normale Route zurück. Das ist in der Praxis ein impliziter Kill Switch.

Teste dein Setup immer von einem tatsächlichen VPN-Client aus (Smartphone über mobile Daten). Lokale Tests vom Server selbst gehen nicht durch die mangle-Chain, weil die Pakete nicht über wg0 reinkommen.


Fazit

Policy-Based Routing mit ProtonVPN und wg-easy ist eine elegante Lösung für ein reales Problem: Du willst nicht deinen gesamten Traffic durch einen kommerziellen VPN jagen, aber für bestimmte Ziele soll deine echte IP verborgen bleiben.

Was du bekommst:

  • Selektives Routing: Nur definierte IPs/Domains gehen über ProtonVPN
  • Keine Auswirkung auf den Rest: Plex-Streams, DNS-Anfragen und normaler Traffic laufen direkt
  • Automatische Aktualisierung: Domain-IPs werden alle 15 Minuten neu aufgelöst
  • Einfache Verwaltung: IPs/Domains in einer Textdatei hinzufügen oder entfernen
  • Überlebt Reboots: Cron-basierter Autostart

Was du brauchst:

  • Eine laufende wg-easy Instanz
  • Einen ProtonVPN-Account
  • 4 Dateien: proton0.conf, routes.conf, pbr-setup.sh, pbr-update.sh
  • 2 Crontab-Einträge

Das Setup ist in wenigen Minuten erledigt und läuft danach wartungsfrei. Wenn du einen neuen Eintrag zur Route-Liste hinzufügen willst, ist das eine Zeile in einer Textdatei - fertig.


Ressourcen

Dieser Eintrag ist vom Autor unter CC BY 4.0 lizensiert.