Homelab WireGuard VPN
WireGuard ist ein schnelles, modernes VPN-Protokoll. Es ist die richtige Wahl für den Fernzugriff auf ein Heimnetzwerk – einfacher zu konfigurieren als OpenVPN und schneller als die meisten Alternativen.
Alle Konfigurationsbeispiele zeigen gängige Einrichtungen. Überprüfe jeden Befehl – insbesondere die iptables-Weiterleitungsregeln und die Dateiberechtigungen für Schlüssel – bevor du sie auf deinem System anwendest, und führe Änderungen in einem Wartungsfenster durch.
Wann verwenden
- Einrichten eines WireGuard-Servers auf einem Raspberry Pi, Linux-Host, pfSense oder Router
- Generieren von WireGuard-Schlüsselpaaren und Schreiben von Peer-Konfigurationsdateien
- Konfigurieren des Fernzugriffs von einem Telefon oder Laptop auf ein Heimnetzwerk
- Erklären von Split-Tunneling (nur Heimverkehr routen) vs. Full-Tunnel (gesamten Verkehr routen)
- Fehlerbehebung bei WireGuard-Verbindungen, die nicht aufgebaut werden
- Automatisieren der Peer-Konfigurationsgenerierung für mehrere Clients
Wie WireGuard funktioniert
Dein Telefon (WireGuard-Client)
│
│ Verschlüsselter UDP-Tunnel (Port 51820)
│
Dein Heimrouter (WireGuard-Server – benötigt eine öffentliche IP oder DDNS)
│
Dein Heimnetzwerk (192.168.1.0/24, NAS, Pi, etc.)
Jedes Gerät hat ein Schlüsselpaar (öffentlicher + privater Schlüssel).
Der Server kennt den öffentlichen Schlüssel jedes Clients.
Der Client kennt den öffentlichen Schlüssel des Servers + Endpunkt (IP:Port).
Der Datenverkehr wird Ende-zu-Ende verschlüsselt, ohne zentralen Server oder Zertifizierungsstelle.
Server-Einrichtung (Linux)
# WireGuard installieren
sudo apt update && sudo apt install wireguard -y
# Server-Schlüsselpaar generieren – Dateien von Anfang an mit privaten Berechtigungen erstellen
sudo mkdir -p /etc/wireguard
sudo sh -c 'umask 077; wg genkey > /etc/wireguard/server_private.key'
sudo sh -c 'wg pubkey < /etc/wireguard/server_private.key > /etc/wireguard/server_public.key'
# Server-Konfiguration schreiben – den tatsächlichen privaten Schlüsselwert ersetzen
# Private Schlüssel nicht in Versionskontrolle speichern oder teilen
sudo tee /etc/wireguard/wg0.conf << 'EOF'
[Interface]
Address = 10.8.0.1/24 # VPN-Subnetz – Server bekommt .1
ListenPort = 51820
PrivateKey = <server_private_key_einfügen>
# Bereichsspezifische Weiterleitungsregeln: VPN-Verkehr rein/raus erlauben, kein pauschales FORWARD ACCEPT
PostUp = iptables -A FORWARD -i wg0 -o eth0 -j ACCEPT
PostUp = iptables -A FORWARD -i eth0 -o wg0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
PostUp = iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
PostDown = iptables -D FORWARD -i wg0 -o eth0 -j ACCEPT
PostDown = iptables -D FORWARD -i eth0 -o wg0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
PostDown = iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE
[Peer]
# Telefon – durch den tatsächlichen öffentlichen Schlüssel des Telefons ersetzen
PublicKey = <telefon_public_key>
AllowedIPs = 10.8.0.2/32
[Peer]
# Laptop – durch den tatsächlichen öffentlichen Schlüssel des Laptops ersetzen
PublicKey = <laptop_public_key>
AllowedIPs = 10.8.0.3/32
EOF
sudo chmod 600 /etc/wireguard/wg0.conf
# Ersetze eth0 durch den tatsächlichen Namen der ausgehenden Schnittstelle
# Überprüfen mit: ip route show default
# IP-Weiterleitung aktivieren (erforderlich für das Routen von Verkehr durch den Server)
echo "net.ipv4.ip_forward=1" | sudo tee /etc/sysctl.d/99-wireguard.conf
sudo sysctl --system
# WireGuard starten und beim Booten aktivieren
sudo wg-quick up wg0
sudo systemctl enable wg-quick@wg0
Client-Konfiguration
# Ein eindeutiges Schlüsselpaar für jedes Client-Gerät generieren
# Auf dem Client oder dem Server ausführen und den privaten Schlüssel sicher übertragen – niemals im Klartext
umask 077
wg genkey | tee phone_private.key | wg pubkey > phone_public.key
# Client-Konfigurationsdatei (phone_wg0.conf):
[Interface]
PrivateKey = <telefon_private_key>
Address = 10.8.0.2/32
DNS = 192.168.1.2 # Optional: Pi-hole für DNS über den Tunnel verwenden
[Peer]
PublicKey = <server_public_key>
Endpoint = your-home-ip.ddns.net:51820 # Deine öffentliche IP oder DDNS-Hostname
AllowedIPs = 192.168.1.0/24 # Split-Tunnel: nur Heimnetzverkehr
# AllowedIPs = 0.0.0.0/0, ::/0 # Full-Tunnel: gesamter Verkehr über VPN
PersistentKeepalive = 25 # NAT-Loch offen halten (erforderlich für mobile Clients)
Split-Tunnel vs. Full-Tunnel
# Split-Tunnel: AllowedIPs = 192.168.1.0/24
Nur Verkehr, der für dein Heimnetzwerk bestimmt ist, geht durch das VPN.
Internetverkehr (YouTube, Spotify) geht direkt – bessere Leistung auf Mobilgeräten.
Am besten für: "Ich möchte einfach von überall auf mein NAS und Pi zugreifen."
# Full-Tunnel: AllowedIPs = 0.0.0.0/0, ::/0
Der GESAMTE Verkehr geht über deine Heim-Internetverbindung.
Nützlich für: Nutzung des heimischen DNS/Pi-hole-Werbeblockers.
Nachteil: Die Upload-Geschwindigkeit zu Hause wird überall zum Engpass.
# Multi-Subnetz-Split-Tunnel (häufigster Homelab-Anwendungsfall):
AllowedIPs = 192.168.10.0/24, 192.168.20.0/24, 192.168.30.0/24, 10.8.0.0/24
Leitet alle deine VLANs durch den Tunnel; Internet bleibt direkt.
Schlüsselerzeugung und Peer-Verwaltung
import subprocess
def generate_keypair() -> tuple[str, str]:
"""Generiert ein WireGuard-Schlüsselpaar. Gibt (privater_schlüssel, öffentlicher_schlüssel) zurück."""
private = subprocess.check_output(["wg", "genkey"]).decode().strip()
public = subprocess.run(
["wg", "pubkey"], input=private.encode(), capture_output=True
).stdout.decode().strip()
return private, public
def generate_preshared_key() -> str:
return subprocess.check_output(["wg", "genpsk"]).decode().strip()
def build_client_config(
client_private_key: str,
client_vpn_ip: str, # z. B. "10.8.0.3"
server_public_key: str,
server_endpoint: str, # z. B. "home.example.com:51820"
allowed_ips: str = "192.168.1.0/24",
dns: str = "",
) -> str:
dns_line = f"DNS = {dns}\n" if dns else ""
return f"""[Interface]
PrivateKey = {client_private_key}
Address = {client_vpn_ip}/32
{dns_line}
[Peer]
PublicKey = {server_public_key}
Endpoint = {server_endpoint}
AllowedIPs = {allowed_ips}
PersistentKeepalive = 25
"""
def build_server_peer_block(
client_public_key: str,
client_vpn_ip: str,
comment: str = "",
) -> str:
comment_line = f"# {comment}\n" if comment else ""
return f"""
{comment_line}[Peer]
PublicKey = {client_public_key}
AllowedIPs = {client_vpn_ip}/32
"""
Halte private Schlüssel außerhalb der Versionskontrolle. Wenn du dieses Skript verwendest, schreibe Schlüsselmaterial in Dateien mit Modus 600 und protokolliere oder drucke es niemals.
pfSense / OPNsense WireGuard
# pfSense: VPN → WireGuard → Tunnel hinzufügen
Interface-Keys: Generieren (erzeugt automatisch ein Schlüsselpaar)
Listen-Port: 51820
Interface-Adresse: 10.8.0.1/24
# Peer hinzufügen (einer pro Client):
Public Key: <client_public_key>
Allowed IPs: 10.8.0.2/32
# WireGuard-Schnittstelle zuweisen:
Interfaces → Assignments → Hinzufügen (wg0 auswählen)
Schnittstelle aktivieren, keine IP benötigt (wird in der Tunnel-Konfiguration gesetzt)
# Firewall-Regeln:
WAN → UDP-Port 51820 eingehend erlauben (damit Clients den Server erreichen können)
WireGuard-Schnittstelle → Verkehr zu LAN-Netzwerken erlauben, die erreichbar sein sollen
DDNS (Dynamic DNS) für Heimserver
Die meisten Heim-Internetverbindungen haben eine dynamische IP. Verwende DDNS, damit dein VPN-Endpunkt auch nach einem IP-Wechsel erreichbar bleibt.
# Option 1: Cloudflare DDNS – Anmeldedaten in einer Secrets-Datei speichern, nicht inline
# docker-compose-Eintrag mit einer Env-Datei:
ddns-updater:
image: qmcgaw/ddns-updater
env_file: ./ddns.env # Zone-ID und Token hier speichern, nicht in compose
restart: unless-stopped
# ddns.env (chmod 600, nicht in Git einchecken):
# SETTINGS_CLOUDFLARE_ZONE_ID=deine_zone_id
# SETTINGS_CLOUDFLARE_TOKEN=dein_api_token
# Option 2: DuckDNS (kostenlos, einfach)
Registrieren auf duckdns.org → Token und Subdomain erhalten (myhome.duckdns.org)
Token in /etc/ddns.env (Modus 600) speichern, dann ein kleines, root-eigenes Skript verwenden:
# /usr/local/bin/update-duckdns
#!/bin/sh
set -eu
. /etc/ddns.env
curl --fail --silent --show-error --max-time 10 \
--get "https://www.duckdns.org/update" \
--data-urlencode "domains=myhome" \
--data-urlencode "token=${DUCKDNS_TOKEN}" \
--data-urlencode "ip="
# Cron-Job:
*/5 * * * * /usr/local/bin/update-duckdns >/dev/null 2>&1
Fehlerbehebung
# WireGuard-Status und letzten Handshake überprüfen
sudo wg show
# Wenn "latest handshake" nie oder sehr alt ist, ist der Tunnel nicht verbunden.
# Überprüfe:
# 1. Ist UDP-Port 51820 auf dem Router/der Firewall geöffnet?
sudo ufw status # oder pfSense/UniFi-Firewall-Regeln überprüfen
# 2. Ist der öffentliche Schlüssel des Servers in der Client-Konfiguration korrekt?
sudo wg show wg0 public-key # Mit dem in der Client-Konfiguration vergleichen
# 3. Ist IP-Weiterleitung auf dem Server aktiviert?
cat /proc/sys/net/ipv4/ip_forward # Sollte 1 sein
# 4. Deckt die Client-AllowedIPs die IP ab, die du erreichen willst?
# Wenn AllowedIPs = 192.168.1.0/24 und du versuchst, 192.168.3.5 zu erreichen, wird es nicht routen.
# Kernel-Logs auf WireGuard-Fehler überprüfen
dmesg | grep wireguard
# WireGuard neu starten
sudo wg-quick down wg0 && sudo wg-quick up wg0
Anti-Patterns
# SCHLECHT: Private Schlüssel in Versionskontrolle speichern oder teilen
# Private Schlüssel sind gleichwertig zu Passwörtern – niemals in Git einchecken
# SCHLECHT: AllowedIPs = 0.0.0.0/0 auf Mobilgeräten ohne Berücksichtigung der Auswirkungen
# Full-Tunnel leitet den gesamten mobilen Datenverkehr über deinen Heim-Upload – normalerweise langsam
# SCHLECHT: PersistentKeepalive auf mobilen Clients nicht setzen
# Mobile Clients hinter NAT lassen inaktive Tunnel ohne Keepalive abbrechen
# SCHLECHT: Port 51820 in der Firewall öffnen, aber IP-Weiterleitung auf dem Server vergessen
# Tunnel verbindet, aber kein Verkehr wird geroutet – verwirrend zu debuggen
# SCHLECHT: Ein Schlüsselpaar für mehrere Client-Geräte teilen
# Jedes Gerät muss sein eigenes eindeutiges Schlüsselpaar haben – geteilte Schlüssel brechen das Sicherheitsmodell
# SCHLECHT: Eine breite "FORWARD ACCEPT"-iptables-Regel verwenden
# Weiterleitungsregeln auf die wg0-Schnittstelle und -Richtung beschränken
Best Practices
- Generiere ein eindeutiges Schlüsselpaar pro Client-Gerät – verwende Schlüssel niemals wieder
- Verwende Split-Tunneling (
AllowedIPs = <heim-subnetze>) für Mobilgeräte - Setze
PersistentKeepalive = 25auf allen mobilen Clients - Verwende DDNS, wenn dein ISP eine dynamische IP vergibt; speichere Anmeldedaten in Env-Dateien, nicht inline
- Verwende bereichsspezifische iptables-Weiterleitungsregeln (nur eingehend auf wg0) anstelle eines pauschalen FORWARD ACCEPT
- Füge die IP des Pi-hole als
DNS =in den Client-Konfigurationen hinzu, um Werbeblockierung über das VPN zu erhalten - Rotiere das Server-Schlüsselpaar regelmäßig und aktualisiere alle Client-Konfigurationen
Verwandte Skills
- homelab-network-setup
- homelab-vlan-segmentation
- homelab-pihole-dns