Die Ausgangssituation
Das Setup entstand aus einer typischen Heimnetz-Realität: Der ISP-Router (DSL-Modem mit integriertem Router) war bereits vorhanden und sollte nicht ersetzt werden. Ein Mesh-System übernimmt WLAN und AP-Funktion im Haus. Die FortiGate kommt dahinter — als echte Firewall für den Server-Bereich.
Das Ergebnis ist eine dreistufige NAT-Kette mit zwei verschiedenen Subnetzen, die voneinander getrennt sind. Geräte im Mesh-Netz können Server im FortiGate-LAN nicht direkt erreichen — das ist gleichzeitig ein Feature und die größte Quelle für Verwirrung beim Setup.
Internet │ DSL / PPPoE ▼ ISP-Router NAT 1 · DHCP 192.168.178.0/24 │ Kabel ▼ Mesh-Master Subnetz 192.168.68.0/22 · DHCP-Server aktiv │ WLAN-Backhaul ▼ Mesh-Node (Büro) kein eigenes Subnetz · nur AP │ Kabel ▼ FortiGate 30E WAN: 203.0.113.10 (DHCP vom Mesh) LAN: 10.0.1.99/24 │ Kabel · LAN-Port ▼ Unmanaged Switch ├── Server1 10.0.1.71 Always-on · Nginx · Pi-hole ├── Server2 10.0.1.82 On-demand · Nextcloud · Paperless └── Server3 10.0.1.83 Always-on · Joplin · AliasVault
Der ISP-Router und der Mesh-Master betreiben beide NAT. Die FortiGate bekommt daher keine öffentliche IP auf dem WAN-Interface, sondern eine private aus dem Mesh-Netz. Das schränkt manche VPN-Szenarien ein, ist für den internen Homelab-Betrieb aber problemlos handhabbar.
Das Mesh-System läuft nicht als reiner AP — es betreibt weiterhin seinen eigenen DHCP und sein eigenes Subnetz (192.168.68.0/22). Smartphones, Laptops und Smart-Home-Geräte hängen im Mesh-Netz. Die FortiGate ist nur für den Server-Bereich zuständig. Die Trennung ist bewusst: Server-Traffic geht durch die Firewall, Consumer-Traffic nicht.
WAN & LAN konfigurieren
Der FortiGate 30E hat zwei relevante Interfaces: wan (Uplink zum Mesh) und lan (Hard-Switch, alle LAN-Ports gebridged). Das WAN-Interface bezieht seine IP per DHCP vom Mesh-Master.
Beim FortiGate 30E heißt das WAN-Interface wan — nicht wan1 wie bei anderen FortiGate-Modellen. In VIP-Konfigurationen muss extintf "wan" stehen. Mit "wan1" greift die VIP nicht und der Traffic kommt nie an. Klingt trivial, kostet aber leicht eine Stunde Debugging.
# WAN Interface — DHCP vom Mesh-Router config system interface edit "wan" set mode dhcp set allowaccess ping https set role wan next # LAN Interface — statisch, Gateway für alle Server edit "lan" set ip 10.0.1.99 255.255.255.0 set allowaccess ping https ssh set role lan next end
| Interface | IP | Modus | Allowaccess |
|---|---|---|---|
| wan | 203.0.113.10 (DHCP) | dhcp | ping, https |
| lan | 10.0.1.99/24 | statisch | ping, https, ssh |
DHCP-Server & Reservierungen
Die FortiGate verteilt IP-Adressen im LAN-Segment. Server bekommen feste Reservierungen per MAC — so bleiben die IPs stabil ohne statische Konfiguration auf jedem Server. Als primärer DNS wird die FortiGate selbst eingetragen, die dann per VIP an Pi-hole weiterleitet.
config system dhcp server edit 1 set interface "lan" set default-gateway 10.0.1.99 set netmask 255.255.255.0 set dns-server1 10.0.1.99 # → Pi-hole via VIP set dns-server2 9.9.9.9 set ip-range start-ip 10.0.1.10 end-ip 10.0.1.210 # DHCP Reservierungen config reserved-address edit 1 set mac aa:bb:cc:dd:ee:01 set ip 10.0.1.71 set description "Server1" next edit 2 set mac aa:bb:cc:dd:ee:02 set ip 10.0.1.83 set description "Server3" next end next end
Als DNS-Server wird 10.0.1.99 eingetragen — also die FortiGate selbst. Per VIP leitet sie DNS-Anfragen (Port 53) intern an Pi-hole weiter. So muss Pi-hole nicht direkt als DNS-Server in jedem Client eingetragen werden, und es gibt einen zentralen Punkt für DNS-Änderungen.
Port-Forwarding via VIPs
Da Geräte im Mesh-Netz (192.168.68.0/22) Server im FortiGate-LAN (10.0.1.0/24) nicht direkt erreichen können, laufen alle Zugriffe über die FortiGate WAN-IP. Virtual IPs (VIPs) leiten eingehende Verbindungen auf bestimmten Ports an interne Dienste weiter.
Der wichtigste VIP-Trick dabei: alle lokalen Domains im Pi-hole zeigen auf die FortiGate WAN-IP — nicht auf die Server direkt. Nginx auf Server1 nimmt dann den HTTPS-Traffic entgegen und verteilt ihn intern.
| VIP-Name | Ext. Port | Interne IP | Int. Port | Dienst |
|---|---|---|---|---|
| VIP-PiHole-DNS | 53 UDP | 10.0.1.71 | 53 | Pi-hole DNS |
| VIP-PiHole-Web | 8888 TCP | 10.0.1.71 | 8888 | Pi-hole Web-UI |
| VIP-HTTP | 80 TCP | 10.0.1.71 | 80 | Nginx → HTTPS-Redirect |
| VIP-HTTPS | 443 TCP | 10.0.1.71 | 443 | Nginx → alle .local-Domains |
| VIP-SSH | 22 TCP | 10.0.1.71 | 22 | SSH Server1 |
| VIP-SSH-S2 | 2222 TCP | 10.0.1.82 | 22 | SSH Server2 |
| VIP-Joplin | 22300 TCP | 10.0.1.83 | 22300 | Joplin Sync (HTTP) |
config firewall vip edit "VIP-HTTPS" set extip 203.0.113.10 # FortiGate WAN-IP set extintf "wan" # ← NICHT "wan1" ! set portforward enable set protocol tcp set extport 443 set mappedip 10.0.1.71 # Nginx auf Server1 set mappedport 443 next end
Ohne VIP-HTTP (Port 80) landen Nutzer die eine URL ohne https:// eintippen auf dem FortiGate-Management-Interface statt auf Nginx. Nginx kann dann den Redirect auf HTTPS machen — aber nur wenn Port 80 auch ankommt.
Traffic erlauben
VIPs alleine leiten noch keinen Traffic. Erst die passende Firewall Policy erlaubt eingehende Verbindungen. Wichtig: NAT muss bei eingehenden VIP-Policies deaktiviert sein, sonst sieht der Ziel-Server die FortiGate als Absender statt den echten Client.
| Policy | Von | Nach | Ziel (VIP) | NAT |
|---|---|---|---|---|
| LAN-to-WAN | lan | wan | all | ACCEPT + NAT |
| HTTPS-to-Nginx | wan | lan | VIP-HTTPS | aus |
| HTTP-to-Nginx | wan | lan | VIP-HTTP | aus |
| DNS-to-Pihole | wan | lan | VIP-PiHole-DNS | aus |
| SSH-Server1 | wan | lan | VIP-SSH | aus |
| Joplin-Sync | wan | lan | VIP-Joplin | aus |
config firewall policy edit 0 set name "HTTPS-to-Nginx" set srcintf "wan" set dstintf "lan" set srcaddr "all" set dstaddr "VIP-HTTPS" set action accept set service "HTTPS" set nat disable # ← wichtig! next end
Pi-hole hinter der FortiGate
Pi-hole läuft auf Server1 im FortiGate-LAN — aber Geräte im Mesh-Netz sollen es trotzdem als DNS-Server nutzen. Die Lösung: DHCP im Mesh-System (oder per FortiGate für LAN-Geräte) trägt die FortiGate WAN-IP als DNS-Server ein. Die FortiGate leitet per VIP-PiHole-DNS alle DNS-Anfragen an Port 53 auf Server1 weiter.
Gerät im Mesh-Netz │ DNS-Anfrage an 203.0.113.10:53 ▼ FortiGate WAN │ VIP-PiHole-DNS → 10.0.1.71:53 ▼ Pi-hole (10.0.1.71) │ filtert · löst lokale Domains auf · upstream: 9.9.9.9 ▼ Antwort: lokale .local-Domain → 203.0.113.10 (FortiGate WAN) │ FortiGate leitet via VIP-HTTPS → Nginx → Dienst
Pi-hole-Einträge für lokale Domains müssen auf die FortiGate WAN-IP zeigen, nicht auf die Server direkt (10.0.1.71). Geräte im Mesh-Netz können 10.0.1.x nicht routen — die Anfrage läuft ins Leere. Erst über die FortiGate WAN-IP mit VIP-HTTPS kommt der Traffic ans Ziel.
| Pi-hole DNS Record | Zeigt auf | Warum |
|---|---|---|
| cloud.local | 203.0.113.10 | FortiGate WAN → VIP-HTTPS → Nginx |
| paperless.local | 203.0.113.10 | FortiGate WAN → VIP-HTTPS → Nginx |
| joplin.local | 203.0.113.10 | FortiGate WAN → VIP-HTTPS → Nginx |
| pihole.local | 203.0.113.10 | FortiGate WAN → VIP-HTTPS → Nginx |
| … alle .local | 203.0.113.10 | Nginx-Vhost entscheidet den Ziel-Dienst |
Reverse Proxy mit mkcert
Nginx läuft als Docker-Container auf Server1 und ist der zentrale Eintrittspunkt für alle HTTPS-Dienste. Ein einziges SAN-Zertifikat (erstellt mit mkcert) deckt alle lokalen Domains ab. Nginx entscheidet per server_name welcher Dienst angesprochen wird — auch wenn der Dienst auf einem anderen Server läuft.
server { listen 443 ssl; server_name joplin.local; ssl_certificate /etc/nginx/certs/homelab.pem; ssl_certificate_key /etc/nginx/certs/homelab-key.pem; location / { proxy_pass http://10.0.1.83:22300; # Server3 proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; } }
Dienste die Nginx cross-server proxyen soll, dürfen nicht auf 127.0.0.1:port binden. Das blockiert Zugriffe von außerhalb des Containers. Stattdessen auf die Server-IP binden: 10.0.1.83:22300:22300 in der Docker Compose ports-Sektion.
Ein einziges Zertifikat für alle .local-Domains spart Verwaltungsaufwand. Die mkcert-Root-CA muss auf jedem Gerät installiert werden das auf die lokalen Domains zugreifen soll — unter Android unter Einstellungen → Sicherheit → CA-Zertifikat installieren.
Was schiefgehen kann — und wie man es fixt
extintf "wan1" statt extintf "wan". Beim FortiGate 30E heißt das Interface wan. Mit dem falschen Namen wird die VIP still ignoriert. Im CLI prüfen mit show firewall vip und den extintf-Wert checken.
10.0.1.71) statt auf die FortiGate WAN-IP. Geräte im Mesh-Netz können das Server-Subnetz nicht routen. Fix: alle lokalen DNS-Einträge in Pi-hole auf <FORTIGATE-WAN-IP> setzen.
host.docker.internal nicht aufgelöst. Fix: in allen Nginx-Configs direkt die Server-IP verwenden statt Hostnamen. Schnell per sed -i 's/host.docker.internal/10.0.1.71/g' conf.d/*.conf erledigt.
127.0.0.1:port:port statt auf die Server-IP. Nginx läuft im eigenen Container und kann localhost des Host-Systems nicht erreichen. In der Docker Compose auf die echte Server-IP binden: 10.0.1.83:8300:80.
VIP-HTTP (Port 80) fehlt oder die zugehörige Firewall Policy ist nicht angelegt. Nginx kann den HTTP→HTTPS-Redirect nur machen wenn Port 80 überhaupt ankommt. VIP + Policy für Port 80 anlegen, Nginx 00-redirect.conf macht den Rest.
docker system prune löscht ungenutzte Netzwerke mit. Das externe proxy-Netzwerk das Nginx mit anderen Containern verbindet muss danach neu angelegt werden: docker network create proxy. Sonst startet Nginx nicht.