Eintrag

VPN-Zugangskontrolle: Granularer Zugriff mit wg-easy Hooks und Firewall-Regeln

VPN-Zugangskontrolle: Granularer Zugriff mit wg-easy Hooks und Firewall-Regeln

Du hast ein WireGuard-VPN mit wg-easy am Laufen und Familie oder Freunde bekommen Zugriff auf Plex und Jellyfin. Aber eigentlich willst du nicht, dass jeder VPN-Client dein gesamtes Netzwerk sehen kann – der Kumpel soll Filme schauen, nicht deinen Proxmox-Cluster erreichen. Die Lösung: ein mehrstufiges Sicherheitskonzept, bei dem vier Schichten zusammenarbeiten, um den Zugriff granular zu kontrollieren.

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.


Das Konzept: 4-Schichten-Sicherheit

Statt sich auf eine einzelne Maßnahme zu verlassen, bauen wir vier unabhängige Sicherheitsschichten auf. Selbst wenn eine Schicht versagt, fangen die anderen den Angriff ab:

flowchart TB
    Client([VPN-Client<br>10.8.0.x]) --> L1
    L1[Schicht 1<br>VPN-DMZ VLAN] --> L2
    L2[Schicht 2<br>Router-Firewall] --> L3
    L3[Schicht 3<br>wg-easy Hooks] --> L4
    L4[Schicht 4<br>NPM Access Lists] --> Services
    Services([Dienste<br>Plex, Jellyfin, Overseerr])

    style L1 fill:#fef3eb,stroke:#f0883e
    style L2 fill:#fcf7ec,stroke:#e3b341
    style L3 fill:#e9fced,stroke:#27c93f
    style L4 fill:#f3f9fb,stroke:#88c0d0
Schicht Was Schützt gegen
1: VPN-DMZ Eigenes VLAN für den VPN-Server Seitliche Bewegung ins LAN
2: Router-FW Inter-VLAN Regeln, Firewall-Gruppen Zugriff auf nicht freigegebene VLANs
3: wg-easy Hooks iptables pro Client (Admin/Restricted) Zugriff auf nicht freigegebene Dienste
4: NPM Access Lists deny/allow pro Proxy Host Zugriff auf Admin-Interfaces über den Reverse Proxy

Jede Schicht funktioniert unabhängig. Vergisst du eine iptables-Regel im Hook, blockiert die Router-Firewall trotzdem den Zugriff. Fehlt eine Firewall-Regel, greift die NPM Access List. Defense in Depth.


Schicht 1: VPN-DMZ – Netzwerk-Isolation

Der erste und wichtigste Schritt: Der WireGuard-Server gehört nicht ins Server-VLAN oder ins Default-LAN. Er bekommt ein eigenes, isoliertes Subnetz.

Warum ein eigenes VLAN?

Wenn wg-easy direkt im Server-VLAN steht, hat jeder VPN-Client automatisch Layer-2-Zugriff auf alle Server. ARP-Scanning, Broadcast-Sniffing – alles möglich. Ein eigenes VLAN erzwingt, dass sämtlicher Traffic durch die Router-Firewall muss.

Setup

Erstelle ein dediziertes VLAN auf deinem Router:

Parameter Beispielwert
VLAN-Name VPN-DMZ
VLAN-ID 45
Subnetz 192.168.45.0/24
Gateway 192.168.45.1
DHCP Optional (wg-easy bekommt eine statische IP)

Die wg-easy VM bekommt eine statische IP in diesem VLAN, z.B. 192.168.45.10.

Die VPN-DMZ sollte kein Internet-Routing per Default haben. Der einzige erlaubte Traffic ist der WireGuard-Port (UDP) von außen und die explizit freigegebenen Verbindungen zum Server-VLAN.

Port Forwarding

Auf dem Router muss ein Port Forward eingerichtet werden, damit externe Clients den WireGuard-Server erreichen:

Externer Port Protokoll Ziel-IP Ziel-Port
51820 UDP 192.168.45.10 51820

Schicht 2: Router-Firewall – Inter-VLAN Regeln

Die Router-Firewall kontrolliert, welche VLANs miteinander kommunizieren dürfen. Standardmäßig sollte kein Inter-VLAN Traffic erlaubt sein (Default Drop).

Firewall-Gruppen

Erstelle IP-Gruppen auf dem Router für die erlaubten Ziele:

Gruppe Mitglieder Zweck
VPN-DMZ 192.168.45.0/24 Quell-VLAN für VPN-Traffic
VPN-SERVICES 192.168.10.50, 192.168.10.51, 192.168.10.52, 192.168.10.100 Erlaubte Ziel-Server

Firewall-Regeln

In den LAN-IN Regeln (oder dem Äquivalent deines Routers):

# Aktion Quelle Ziel Protokoll Beschreibung
1 Allow VPN-DMZ VPN-SERVICES TCP/UDP VPN-Clients → erlaubte Dienste
2 Allow VPN-SERVICES VPN-DMZ TCP/UDP (established) Antwort-Traffic zurück
3 Drop VPN-DMZ RFC1918 Alle Alles andere blockieren

Diese Regeln funktionieren auf jedem Router der Inter-VLAN Firewall unterstützt – UniFi, pfSense, OPNsense, MikroTik. Die Syntax unterscheidet sich, das Konzept bleibt gleich.

Ohne VLAN-fähigen Router

Falls dein Router keine VLANs unterstützt, kannst du die wg-easy VM in ein separates Subnetz stecken und statische Routen verwenden. Die Firewall-Regeln werden dann direkt auf dem WireGuard-Host (Schicht 3) umso wichtiger.


Schicht 3: wg-easy Hooks – Per-Client iptables-Regeln

Das Herzstück der Zugangskontrolle. wg-easy v15 unterstützt Per-Client Hooks – jeder Client kann eigene PostUp/PostDown-Regeln bekommen. Damit definierst du pro Person, was erlaubt ist.

Zwei Zugangsstufen

Stufe Für wen Zugriff
Admin Du selbst Voller Zugriff auf alle VLANs und Services
Restricted Familie, Freunde Nur explizit freigegebene Dienste (Whitelist)

Variablen in wg-easy v15

wg-easy stellt in den Hooks Platzhalter bereit:

Variable Bedeutung Beispiel
{{ipv4Cidr}} WireGuard-Subnetz 10.8.0.0/24
{{device}} Netzwerk-Interface des Hosts eth0
{{port}} WireGuard Listen-Port 51820

Admin-Hook (unrestricted)

Für Admin-Clients: volle FORWARD-Erlaubnis in beide Richtungen.

PostUp:

1
2
3
iptables -A INPUT -p udp -m udp --dport  -j ACCEPT
iptables -A FORWARD -i wg0 -j ACCEPT
iptables -A FORWARD -o wg0 -j ACCEPT

PostDown (spiegelbildlich mit -D statt -A):

1
2
3
iptables -D INPUT -p udp -m udp --dport  -j ACCEPT
iptables -D FORWARD -i wg0 -j ACCEPT
iptables -D FORWARD -o wg0 -j ACCEPT

Statt MASQUERADE wird auf dem Router eine statische Route eingerichtet (10.8.0.0/24 → 192.168.45.10). So bleiben die echten Client-IPs erhalten und NPM Access Lists können pro Client greifen. Ohne statische Route koennten die Zielserver die Antwortpakete nicht an die VPN-Client-IP (z.B. 10.8.0.3) zurueckrouten.

Restricted-Hook (Whitelist)

Hier wird es interessant. Der Restricted-Hook arbeitet nach dem Whitelist-Prinzip:

  1. ESTABLISHED,RELATED – Antwortpakete erlauben (Stateful Firewall)
  2. ACCEPT pro Dienst – Einzelne Ziel-IP + Port Kombination erlauben
  3. DROP als Default – Alles andere wird geblockt

PostUp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# Basis
iptables -A INPUT -p udp -m udp --dport  -j ACCEPT
iptables -A FORWARD -i wg0 -j ACCEPT
iptables -A FORWARD -o wg0 -j ACCEPT

# Stateful Tracking (Antwortpakete)
iptables-nft -I FORWARD 1 -o wg0 -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT

# Whitelist: Erlaubte Dienste
iptables-nft -I FORWARD 2 -i wg0 -d 192.168.10.50 -p tcp --dport 443 -j ACCEPT    # NPM (Reverse Proxy)
iptables-nft -I FORWARD 3 -i wg0 -d 192.168.10.100 -p tcp --dport 32400 -j ACCEPT  # Plex
iptables-nft -I FORWARD 4 -i wg0 -d 192.168.10.100 -p tcp --dport 8096 -j ACCEPT   # Jellyfin
iptables-nft -I FORWARD 5 -i wg0 -d 192.168.10.100 -p tcp --dport 5055 -j ACCEPT   # Overseerr
iptables-nft -I FORWARD 6 -i wg0 -d 192.168.10.51 -p udp --dport 53 -j ACCEPT     # Pi-Hole DNS
iptables-nft -I FORWARD 7 -i wg0 -d 192.168.10.51 -p tcp --dport 53 -j ACCEPT     # Pi-Hole DNS
iptables-nft -I FORWARD 8 -i wg0 -d 192.168.10.52 -p udp --dport 53 -j ACCEPT     # Pi-Hole DNS (Backup)
iptables-nft -I FORWARD 9 -i wg0 -d 192.168.10.52 -p tcp --dport 53 -j ACCEPT     # Pi-Hole DNS (Backup)

# Default: Alles andere blockieren
iptables-nft -I FORWARD 10 -i wg0 -j DROP

In wg-easy trägst du die Hooks als eine Zeile ein, getrennt durch ;. Die mehrzeilige Darstellung hier dient nur der Lesbarkeit.

PostDown (jede Regel mit -D statt -I / -A):

1
2
3
4
5
6
7
8
9
10
11
12
13
iptables -D INPUT -p udp -m udp --dport  -j ACCEPT
iptables -D FORWARD -i wg0 -j ACCEPT
iptables -D FORWARD -o wg0 -j ACCEPT
iptables-nft -D FORWARD -o wg0 -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
iptables-nft -D FORWARD -i wg0 -d 192.168.10.50 -p tcp --dport 443 -j ACCEPT
iptables-nft -D FORWARD -i wg0 -d 192.168.10.100 -p tcp --dport 32400 -j ACCEPT
iptables-nft -D FORWARD -i wg0 -d 192.168.10.100 -p tcp --dport 8096 -j ACCEPT
iptables-nft -D FORWARD -i wg0 -d 192.168.10.100 -p tcp --dport 5055 -j ACCEPT
iptables-nft -D FORWARD -i wg0 -d 192.168.10.51 -p udp --dport 53 -j ACCEPT
iptables-nft -D FORWARD -i wg0 -d 192.168.10.51 -p tcp --dport 53 -j ACCEPT
iptables-nft -D FORWARD -i wg0 -d 192.168.10.52 -p udp --dport 53 -j ACCEPT
iptables-nft -D FORWARD -i wg0 -d 192.168.10.52 -p tcp --dport 53 -j ACCEPT
iptables-nft -D FORWARD -i wg0 -j DROP

Paketfluss für Restricted-Clients

flowchart LR
    A[VPN-Client<br>10.8.0.3] -->|Paket an<br>192.168.10.100:32400| B[wg0]
    B -->|Quell-IP bleibt<br>10.8.0.3| C{iptables<br>FORWARD}
    C -->|ESTABLISHED?| D[Nein - neuer Flow]
    D -->|Match Regel 3<br>Plex 32400/tcp| E[ACCEPT]
    E --> F[Plex Server<br>192.168.10.100]

    style A fill:#e1f5ff
    style E fill:#e1ffe1
    style F fill:#fff3e0
flowchart LR
    A[VPN-Client<br>10.8.0.3] -->|Paket an<br>192.168.10.15:8006| B[wg0]
    B --> C{iptables<br>FORWARD}
    C -->|Kein Match<br>in Regeln 1-9| D[Regel 10:<br>DROP]

    style A fill:#e1f5ff
    style D fill:#ffe1e1

Schicht 4: NPM Access Lists – Reverse Proxy absichern

Die wg-easy Hooks kontrollieren, welche IPs und Ports erreichbar sind. Aber was ist mit Diensten, die hinter dem Reverse Proxy laufen? Wenn ein VPN-Client Port 443 des Reverse Proxy erreichen darf (weil dort Plex und Jellyfin laufen), kann er theoretisch auch jedes andere Proxy-Ziel aufrufen – inklusive Admin-Interfaces wie Proxmox, Portainer oder die Router-UI.

Hier kommen NPM Access Lists ins Spiel.

Access List erstellen

In Nginx Proxy Manager: Access Lists > Add Access List

Feld Wert
Name Block VPN Clients
Satisfy Any Ja

Im Tab Access:

1
2
3
deny  10.10.10.0/24    # wg-easy Instanz 1 Subnetz
deny  10.8.0.0/24      # wg-easy Instanz 2 Subnetz
allow 0.0.0.0/0        # Alle anderen erlauben

Access List zuweisen

Die Access List wird den Proxy Hosts zugewiesen, die VPN-Clients nicht erreichen sollen:

Proxy Host Access List Grund
proxmox.example.com Block VPN Clients Hypervisor-Admin
portainer.example.com Block VPN Clients Container-Admin
npm.example.com Block VPN Clients Reverse Proxy Admin
pihole.example.com Block VPN Clients DNS-Admin
router.example.com Block VPN Clients Router-Admin

Proxy Hosts die VPN-Clients erreichen duerfen, bekommen keine Access List:

Proxy Host Access List Grund
plex.example.com Keine Streaming erlaubt
jellyfin.example.com Keine Streaming erlaubt
overseerr.example.com Keine Medienwuensche erlaubt

Admins die sich ueber VPN einloggen und trotzdem auf Admin-Interfaces zugreifen muessen, kannst du per allow-Regel in der Access List ausnehmen: allow 10.8.0.2 (spezifische Admin-VPN-IP).

Wie NPM die Quell-IP erkennt

Damit die Access Lists die VPN-Clients korrekt filtern koennen, muss NPM die echte Client-IP sehen. Der empfohlene Weg ist eine statische Route auf dem Router – alternativ gibt es den MASQUERADE-Ansatz als Fallback:

Ansatz A: Kein MASQUERADE (empfohlen)

Statt MASQUERADE richtest du eine statische Route auf dem Router ein:

Subnetz Gateway Beschreibung
10.8.0.0/24 192.168.45.10 Route VPN-Traffic zum wg-easy Server

Vorteil: NPM sieht die echte VPN-Client-IP (z.B. 10.8.0.3), Access Lists und Logs werden aussagekraeftiger.

Ansatz B: Mit MASQUERADE

Falls statische Routen nicht moeglich sind, blockierst du in der Access List die wg-easy Server-IP:

1
2
deny  192.168.45.10    # wg-easy Server IP
allow 0.0.0.0/0

Nachteil: Alle VPN-Clients werden gleich behandelt, keine Unterscheidung zwischen Admin und Restricted.


Services erweitern: Neuen Dienst freigeben

Dein Kumpel will jetzt auch Nextcloud ueber VPN nutzen, oder du richtest Audiobookshelf fuer Hoerbuecher ein. Jedes Mal sind bis zu drei Schichten betroffen. Hier die vollstaendige Anleitung am Beispiel von Nextcloud (192.168.10.200:443).

Schritt 1: Router-Firewall pruefen (Schicht 2)

Ist die IP des neuen Dienstes bereits in deiner VPN-SERVICES Firewall-Gruppe? Falls der Dienst auf einem Server laeuft, der schon in der Gruppe ist (z.B. gleiche IP wie Plex), kannst du diesen Schritt ueberspringen.

Falls nicht: Fuege die IP zur Firewall-Gruppe hinzu.

Situation Aktion
Dienst auf bekanntem Server (z.B. 192.168.10.100) Nichts zu tun
Dienst auf neuem Server (z.B. 192.168.10.200) IP zur VPN-SERVICES Gruppe hinzufuegen

Schritt 2: wg-easy Hook anpassen (Schicht 3)

Das ist der wichtigste Schritt. Du fuegst eine neue ACCEPT-Regel in den Restricted-Hook ein – und zwar vor der DROP-Regel.

Vorher (DROP auf Position 10):

1
2
iptables-nft -I FORWARD 9 -i wg0 -d 192.168.10.52 -p tcp --dport 53 -j ACCEPT     # Pi-Hole DNS (Backup)
iptables-nft -I FORWARD 10 -i wg0 -j DROP                                           # Default DROP

Nachher (Nextcloud auf Position 10, DROP rueckt auf 11):

1
2
3
iptables-nft -I FORWARD 9 -i wg0 -d 192.168.10.52 -p tcp --dport 53 -j ACCEPT     # Pi-Hole DNS (Backup)
iptables-nft -I FORWARD 10 -i wg0 -d 192.168.10.200 -p tcp --dport 443 -j ACCEPT   # Nextcloud
iptables-nft -I FORWARD 11 -i wg0 -j DROP                                           # Default DROP

Vergiss nicht, die gleiche Regel auch im PostDown mit -D hinzuzufuegen:

1
iptables-nft -D FORWARD -i wg0 -d 192.168.10.200 -p tcp --dport 443 -j ACCEPT

Die Positionsnummern (-I FORWARD 10) bestimmen die Reihenfolge. Jede neue Regel erhoet die Position der DROP-Regel um 1. Wenn du mehrere Dienste gleichzeitig hinzufuegst, zaehle sorgfaeltig durch.

Schritt 3: NPM Access List pruefen (Schicht 4)

Falls der neue Dienst hinter dem Reverse Proxy laeuft:

Situation Aktion
Neuer Proxy Host fuer Nextcloud Keine Access List zuweisen (VPN-Clients duerfen zugreifen)
Bestehender Proxy Host hat “Block VPN Clients” Access List entfernen, damit VPN-Clients durchkommen
Dienst laeuft nicht hinter NPM (direkte IP) Kein NPM-Schritt noetig

Schritt 4: Testen

  1. VPN trennen und neu verbinden – die Hooks greifen nur beim Verbindungsaufbau
  2. Dienst aufrufenhttps://nextcloud.example.com oder direkt https://192.168.10.200
  3. Admin-UIs gegenpruefen – stelle sicher, dass Proxmox, Portainer etc. weiterhin blockiert sind
1
2
3
4
5
6
# Auf dem wg-easy Host: Regeln pruefen
sudo iptables-nft -L FORWARD -n --line-numbers

# Erwartete Ausgabe (Ausschnitt):
# 10   ACCEPT  tcp  --  *  wg0  0.0.0.0/0  192.168.10.200  tcp dpt:443
# 11   DROP    all  --  *  wg0  0.0.0.0/0  0.0.0.0/0

Kurzreferenz: Typische Dienste

Hier eine Uebersicht gaengiger Self-Hosted Dienste und die noetige Konfiguration:

Dienst IP:Port iptables-Regel NPM Access List
Nextcloud 192.168.10.200:443 ACCEPT tcp dpt:443 Keine (Zugriff erlaubt)
Audiobookshelf 192.168.10.100:13378 ACCEPT tcp dpt:13378 Keine
Immich 192.168.10.100:2283 ACCEPT tcp dpt:2283 Keine
Vaultwarden 192.168.10.100:8080 ACCEPT tcp dpt:8080 Keine
Grafana 192.168.10.100:3000 ACCEPT tcp dpt:3000 Block VPN Clients
Portainer 192.168.10.100:9443 Kein Eintrag (blockiert) Block VPN Clients

Dienste die sowohl fuer Restricted-Clients freigegeben als auch hinter NPM laufen, brauchen Eintraege in beiden Schichten: iptables-Regel UND NPM Proxy Host ohne Access List. Vergisst du eine Schicht, kommt der Client nicht durch.

Mehrere Dienste auf einmal hinzufuegen

Wenn du gleich drei oder vier Dienste freigibst, kann das Durchzaehlen der Regelpositionen fehleranfaellig werden. Ein Trick: Fuege alle neuen ACCEPT-Regeln mit aufsteigenden Positionsnummern ein und setze die DROP-Regel ganz am Ende.

1
2
3
4
5
# Drei neue Dienste: Nextcloud, Audiobookshelf, Immich
iptables-nft -I FORWARD 10 -i wg0 -d 192.168.10.200 -p tcp --dport 443 -j ACCEPT    # Nextcloud
iptables-nft -I FORWARD 11 -i wg0 -d 192.168.10.100 -p tcp --dport 13378 -j ACCEPT  # Audiobookshelf
iptables-nft -I FORWARD 12 -i wg0 -d 192.168.10.100 -p tcp --dport 2283 -j ACCEPT   # Immich
iptables-nft -I FORWARD 13 -i wg0 -j DROP                                            # Default DROP

Und die passenden PostDown-Regeln:

1
2
3
iptables-nft -D FORWARD -i wg0 -d 192.168.10.200 -p tcp --dport 443 -j ACCEPT
iptables-nft -D FORWARD -i wg0 -d 192.168.10.100 -p tcp --dport 13378 -j ACCEPT
iptables-nft -D FORWARD -i wg0 -d 192.168.10.100 -p tcp --dport 2283 -j ACCEPT

Bonus: Selektives VPN-Routing ueber ProtonVPN

Optional kannst du bestimmten Traffic deiner VPN-Clients durch einen kommerziellen VPN-Provider (z.B. ProtonVPN) routen. Details zum kompletten Setup findest du im separaten Post: Selektives VPN-Routing: Bestimmte IPs ueber ProtonVPN tunneln.

Kurzzusammenfassung: Mit Policy-Based Routing (PBR) kannst du eine Liste von IPs/Domains definieren, deren Traffic ueber einen zweiten WireGuard-Tunnel (ProtonVPN) laeuft. Der Rest geht normal raus. So schuetzt du die Privatsphaere deiner Nutzer bei bestimmten Diensten, ohne den gesamten Traffic zu verlangsamen.

flowchart LR
    A[VPN-Client] --> B[wg0]
    B --> C{Ziel in<br>Route-Liste?}
    C -->|Nein| D[Normales Routing<br>Heim-IP]
    C -->|Ja| E[ProtonVPN-Tunnel<br>proton0]
    E --> F[Internet<br>ProtonVPN-IP]
    D --> G[Internet<br>Heim-IP]

    style E fill:#f5ecfd,stroke:#bf5af2
    style D fill:#e4f9e8,stroke:#27c93f

Wie PBR funktioniert

Auf dem wg-easy Host laeuft ein zweiter WireGuard-Tunnel (proton0) zum ProtonVPN-Server. Ein ipset enthaelt alle Ziel-IPs, die ueber diesen Tunnel geroutet werden sollen. Die Entscheidung faellt pro Paket in der iptables mangle-Chain:

  1. Paket kommt auf wg0 rein (vom VPN-Client)
  2. ipset-Lookup: Ist die Ziel-IP in der Route-Liste? (ipset test vpn-routes <ip>)
  3. Ja: Paket bekommt ein fwmark (Markierung 0x1)
  4. IP Rule: Markierte Pakete nutzen Routing-Tabelle 100 statt der Main-Tabelle
  5. Tabelle 100: Default-Route zeigt auf proton0 → Paket geht durch den ProtonVPN-Tunnel
  6. MASQUERADE auf proton0: Quell-IP wird zur Tunnel-IP umgeschrieben

Das ipset-Lookup ist O(1) dank Hash-Tabelle – selbst mit hunderten Eintraegen gibt es keine spuerbare Latenz.

Die Route-Liste: routes.conf

Alle Ziele werden in einer einfachen Textdatei verwaltet:

1
2
3
4
5
6
7
8
9
10
# /opt/wg-easy/proton-pbr/routes.conf

# === Direkte IPs ===
178.17.166.194
185.195.236.11
103.214.7.28

# === Domains (werden automatisch aufgeloest) ===
# example-tracker.org
# streaming-service.net

Format:

  • Eine IP oder Domain pro Zeile
  • Kommentare mit #
  • IPv4-Adressen werden direkt ins ipset uebernommen
  • Domains werden per dig +short <domain> A aufgeloest – alle resultierenden IPs landen im ipset

Neue Ziele hinzufuegen

Eine IP hinzufuegen:

1
2
3
4
5
6
7
8
# 1. IP in die Route-Liste eintragen
echo "203.0.113.42" | sudo tee -a /opt/wg-easy/proton-pbr/routes.conf

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

# 3. Pruefen ob die IP im ipset ist
sudo ipset test vpn-routes 203.0.113.42

Eine Domain hinzufuegen:

1
2
3
4
5
6
7
8
9
# 1. Domain eintragen
echo "tracker-forum.example.org" | sudo tee -a /opt/wg-easy/proton-pbr/routes.conf

# 2. Anwenden -- das Script loest die Domain auf und fuegt alle IPs hinzu
sudo /opt/wg-easy/proton-pbr/pbr-update.sh

# 3. Pruefen welche IPs aufgeloest wurden
dig +short tracker-forum.example.org A
sudo ipset list vpn-routes

Domains sind praktisch fuer Dienste mit wechselnden IPs (CDNs, Load Balancer). Der Cron-Job loest sie alle 15 Minuten neu auf. Feste IPs sind zuverlaessiger und sofort aktiv.

Einen Eintrag entfernen:

1
2
3
4
5
# 1. Zeile aus routes.conf loeschen (z.B. mit sed oder Editor)
sudo sed -i '/203.0.113.42/d' /opt/wg-easy/proton-pbr/routes.conf

# 2. Update-Script laedt das ipset komplett neu
sudo /opt/wg-easy/proton-pbr/pbr-update.sh

Automatische Aktualisierung

Zwei Cron-Jobs sorgen dafuer, dass das PBR immer aktuell ist:

Cron Timing Aufgabe
@reboot sleep 30 && pbr-setup.sh Nach jedem Boot Tunnel starten, ipset erstellen, iptables-Regeln setzen
*/15 * * * * pbr-update.sh Alle 15 Minuten Domains neu aufloesen, ipset aktualisieren

Die 30 Sekunden Verzoegerung beim Boot gibt Docker und wg-easy Zeit zum Starten. Das 15-Minuten-Intervall fuer Updates ist ein guter Kompromiss: schnell genug fuer DNS-Aenderungen, ohne unnoetige Last.

Logs pruefen:

1
2
tail -f /var/log/pbr-setup.log    # Boot-Log
tail -f /var/log/pbr-update.log   # Update-Log

Verifizierung

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

# Exit-IP pruefen (sollte ProtonVPN-IP zeigen, nicht deine WAN-IP)
sudo curl --interface proton0 https://ifconfig.me

# Aktueller Inhalt des ipsets
sudo ipset list vpn-routes

# Routing-Tabelle 100 korrekt?
sudo ip route show table 100
# Erwartete Ausgabe: default dev proton0 scope link

# IP-Rule gesetzt?
sudo ip rule show | grep fwmark
# Erwartete Ausgabe: 100: from all fwmark 0x1 lookup 100

# Paketzaehler der mangle-Regel (sollte mit Traffic steigen)
sudo iptables -t mangle -L PREROUTING -n -v

PBR laesst sich nicht vom wg-easy Host selbst testen – nur Traffic der ueber das wg0-Interface reinkommt, wird markiert. Teste immer von einem VPN-Client aus.


Alles zusammen: Komplettes Beispiel

Hier ein vollstaendiges Beispiel-Setup fuer einen WireGuard-Server mit wg-easy v15 und allen vier Sicherheitsschichten:

Netzwerk-Uebersicht

flowchart TB
    Internet([Internet]) -->|Port 51820/UDP| Router[Router / Firewall<br>192.168.45.1]

    Router -->|VLAN 45| WG[wg-easy Server<br>192.168.45.10<br>Subnet: 10.8.0.0/24]

    Router -->|VLAN 60<br>Inter-VLAN FW| Server[Server-VLAN<br>192.168.10.0/24]

    WG -.->|Admin Hook<br>ACCEPT all| Server
    WG -.->|Restricted Hook<br>Whitelist only| NPM[NPM<br>192.168.10.50:443]

    NPM --> Plex[Plex<br>:32400]
    NPM --> Jellyfin[Jellyfin<br>:8096]
    NPM --> Overseerr[Overseerr<br>:5055]
    NPM -.->|Access List:<br>VPN blockiert| Proxmox[Proxmox<br>Admin-UI]
    NPM -.->|Access List:<br>VPN blockiert| Portainer[Portainer<br>Admin-UI]

    Server --> DNS[Pi-Hole DNS<br>:53]

    style WG fill:#fcf0e3,stroke:#f0883e
    style NPM fill:#edf7fa,stroke:#88c0d0
    style Proxmox fill:#ffedeb,stroke:#ff5f56
    style Portainer fill:#ffedeb,stroke:#ff5f56

docker-compose.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
services:
  wg-easy:
    image: ghcr.io/wg-easy/wg-easy:15
    container_name: wg-easy
    hostname: wg-easy
    network_mode: host
    environment:
      - PORT=51821
      - WG_HOST=vpn.example.com
      - WG_PATH=/
      - DISABLE_IPV6=true
    volumes:
      - ./data:/etc/wireguard
    cap_add:
      - NET_ADMIN
      - SYS_MODULE
    restart: unless-stopped

wg-easy v15 mit network_mode: host braucht Sysctls auf dem Host, nicht im Container. Erstelle /etc/sysctl.d/99-wireguard.conf auf dem Docker-Host.

1
2
net.ipv4.ip_forward = 1
net.ipv4.conf.all.src_valid_mark = 1
1
sudo sysctl -p /etc/sysctl.d/99-wireguard.conf

Checkliste fuer das Setup

  • VLAN erstellen – Eigenes VPN-DMZ Subnetz auf dem Router
  • VM/Container – wg-easy VM in der VPN-DMZ mit statischer IP
  • Port Forward – WireGuard UDP-Port vom WAN zur wg-easy IP
  • Firewall-Gruppen – VPN-DMZ und VPN-SERVICES Gruppen anlegen
  • Inter-VLAN Regeln – Allow VPN-DMZ → Services, Drop VPN-DMZ → Rest
  • Statische Route – VPN-Subnet → wg-easy IP (fuer NPM Access Lists)
  • wg-easy einrichten – Container starten, Setup Wizard durchlaufen
  • Admin-Hook – Fuer dich: voller Zugriff
  • Restricted-Hook – Fuer Gaeste: nur Whitelist-Dienste
  • NPM Access List – VPN-Subnets bei Admin-Proxy-Hosts blockieren
  • DNS konfigurieren – VPN-Clients nutzen dein Pi-Hole als DNS
  • Testen – Restricted-Client darf Plex erreichen, aber nicht Proxmox

Troubleshooting

VPN-Client kann nichts erreichen

1
2
3
4
5
# Auf dem wg-easy Host: Sind die iptables-Regeln aktiv?
sudo iptables-nft -L FORWARD -n --line-numbers

# Laeuft der WireGuard-Tunnel?
sudo wg show wg0

Pruefe die Reihenfolge: Wenn die DROP-Regel vor den ACCEPT-Regeln steht, wird alles blockiert. Die Regelnummern in -I FORWARD <NR> sind entscheidend.

Restricted-Client erreicht mehr als erlaubt

1
2
3
4
5
# Welche FORWARD-Regeln sind aktiv?
sudo iptables-nft -L FORWARD -n --line-numbers -v

# Gibt es eine ACCEPT-all Regel die zu frueht greift?
# -> Position pruefen, DROP muss die LETZTE Regel sein

NPM Access List greift nicht

  1. Pruefe ob der VPN-Client mit seiner echten IP ankommt:
    1
    2
    
    # In den NPM Access Logs nach der Quell-IP schauen
    tail -f /data/logs/proxy-host-*_access.log | grep "10.8.0"
    
  2. Falls du die wg-easy Server-IP (z.B. 192.168.45.10) statt der Client-IP siehst, ist vermutlich MASQUERADE aktiv oder die statische Route auf dem Router fehlt. Pruefe die Route (VPN-Subnet → wg-easy IP) und stelle sicher, dass kein MASQUERADE in den wg-easy Hooks steht.

DNS funktioniert nicht ueber VPN

Im wg-easy Admin Panel: WireGuard > DNS Servers muss auf die Pi-Hole IPs zeigen (z.B. 192.168.10.51, 192.168.10.52). Diese IPs muessen auch in den iptables-Hooks erlaubt sein (Port 53 UDP + TCP).

Android-Nutzer aufgepasst: “Privates DNS” in den Android-Einstellungen muss auf Aus stehen. Sonst nutzt Android DNS-over-TLS und umgeht den VPN-Tunnel komplett.


Zusammenfassung

Schicht Werkzeug Konfiguration Granularitaet
1: VPN-DMZ Router VLAN Eigenes Subnetz Pro Netzwerk-Segment
2: Router-FW Firewall-Gruppen + Regeln Inter-VLAN Allow/Drop Pro VLAN/IP-Gruppe
3: Hooks wg-easy iptables PostUp/PostDown pro Client Pro Client + Dienst (IP:Port)
4: NPM ACL Access Lists deny/allow pro Proxy Host Pro Web-Dienst + Quell-IP

Die Kombination dieser vier Schichten gibt dir die volle Kontrolle: Dein Kumpel schaut Filme auf Plex, deine Schwester nutzt Jellyfin, und keiner von beiden kann versehentlich (oder absichtlich) deinen Proxmox-Cluster oder die Router-UI erreichen. Und falls du doch mal jemandem mehr Rechte geben willst, aenderst du einfach den Hook von Restricted auf Admin.

Dieser Eintrag ist vom Autor unter CC BY 4.0 lizensiert.