Feste IPv4 trotz CGNAT: NAT-Bridge über einen günstigen VPS mit WireGuard
Dein Provider gibt dir nur eine CGNAT-Adresse? So baust du dir mit einem günstigen VPS und WireGuard eine NAT-Bridge und erreichst dein Heimnetz über eine feste öffentliche IPv4.
Problem
Du möchtest eine feste IPv4-Adresse für deine selbst gehosteten Dienste, bekommst von deinem Provider aber nur eine CGNAT-Adresse (Carrier-grade NAT)? Wenn im Router bei IPv4 eine Adresse wie 100.64.74.132 steht, hast du keine eigene öffentliche IPv4 zugewiesen bekommen, sondern teilst sie dir mit anderen Kunden. Was du hingegen fast immer bekommst, ist ein eigenes IPv6-Subnetz.
Lösung
Du baust dir mit einem günstigen vServer (ab ca. 1 €) eine NAT-Bridge und leitest jeglichen IPv4-Traffic über diesen Server weiter. Alternativ bekommst du über Oracle Free Tier (AMD Compute Instance) einen kostenlosen VPS. Der Zugriff auf dein Heimnetz erfolgt anschließend über die öffentliche IPv4 deines VPS. Der Datenverkehr läuft dabei verschlüsselt durch einen WireGuard-VPN-Tunnel.
Einrichtung auf dem VPS
Die Installation wurde auf einem Ubuntu-24.04-Host durchgeführt.
System aktualisieren:
apt-get update && apt-get upgrade -yPiVPN installieren:
curl -L https://install.pivpn.io | bashNamen des Netzwerk-Interfaces herausfinden:
ip aDie WireGuard-Konfiguration bearbeiten:
nano /etc/wireguard/wg0.confDirekt nach der Zeile ListenPort folgende Zeile einfügen (bei dein_interface den zuvor ermittelten Interface-Namen eintragen):
PostUp = iptables -A FORWARD -i %i -j ACCEPT; iptables -t nat -A POSTROUTING -o dein_interface -j MASQUERADEDanach das System neu starten und ein Client-Profil anlegen:
pivpn -aEinen Namen für das Profil vergeben, z. B. heimnetzwerk.
Die Konfiguration erneut öffnen:
nano /etc/wireguard/wg0.confBei AllowedIPs den IP-Bereich deines Heimnetzwerks ergänzen:
AllowedIPs = 10.70.193.2/32, 192.168.8.0/24Den Inhalt der erzeugten Client-Konfiguration vorübergehend zwischenspeichern (wird später auf der VM gebraucht):
cat /etc/wireguard/configs/heimnetzwerk.confIP-Forwarding aktivieren – dazu die sysctl-Konfiguration bearbeiten:
nano /etc/sysctl.confDie Zeile #net.ipv4.ip_forward = 1 auskommentieren, sodass daraus net.ipv4.ip_forward = 1 wird.
Ein Skript für die NAT-Regeln anlegen:
nano /ip.shFolgenden Inhalt eintragen:
#!/bin/bash
iptables -t nat -A POSTROUTING -j MASQUERADEUm alle Ports an 192.168.8.14 weiterzuleiten (in diesem Fall der Nginx Proxy Manager):
iptables -A PREROUTING -t nat -p tcp --match multiport ! --dports 22,51820 -j DNAT --to-destination 192.168.8.14
iptables -A PREROUTING -t nat -p udp --match multiport ! --dports 22,51820 -j DNAT --to-destination 192.168.8.14Um nur einen einzelnen Port (80) an 192.168.8.14 weiterzuleiten (in diesem Fall ein Webserver):
iptables -t nat -A PREROUTING -p "tcp" --dport "80" -j DNAT --to-destination "192.168.8.14":"80"iptables-Regeln dauerhaft speichern:
sudo apt install netfilter-persistent
sudo netfilter-persistent save
sudo systemctl enable netfilter-persistentDas Skript ausführbar machen:
chmod +x /ip.shCrontab öffnen:
crontab -eUnd folgende Zeile eintragen, damit die Regeln bei jedem Neustart gesetzt werden:
@reboot /ip.shNach einem finalen Reboot ist die Einrichtung auf dem VPS abgeschlossen.
Einrichtung auf der VM (Heimnetz)
System aktualisieren und WireGuard installieren:
apt-get update && apt-get upgrade -y && apt-get install -y wireguard openresolv wireguard-dkmsDie WireGuard-Konfiguration anlegen und den Inhalt aus /etc/wireguard/configs/heimnetzwerk.conf (vom VPS) einfügen:
nano /etc/wireguard/wg0.confVor dem Abschnitt [Peer] folgende Zeilen einfügen und das Interface anpassen (bei dein_interface):
PostUp = iptables -A FORWARD -i wg0 -j ACCEPT
PostUp = iptables -t nat -A POSTROUTING -o dein_interface -j MASQUERADE
PostUp = echo 1 > /proc/sys/net/ipv4/ip_forwardAm Ende der Datei außerdem folgende Zeile ergänzen, damit der Tunnel dauerhaft offen bleibt:
PersistentKeepalive = 25WireGuard aktivieren:
systemctl enable wg-quick@wg0Nach einem Reboot ist die VM fertig eingerichtet.
IONOS – Firewall-Anpassungen
Im Beispiel mit IONOS muss in der Firewall eine Regel für den WireGuard-Port hinterlegt werden.


Optionale Anpassungen
DNS für den WireGuard-Tunnel aktivieren
DNS in /etc/wireguard/wg0.conf hinterlegen:
DNS = 9.9.9.9Anschließend die Datei /etc/systemd/resolved.conf.d/disable-stub.conf mit folgendem Inhalt anlegen:
[Resolve]
DNSStubListener=noDanach systemd-resolved neu starten:
sudo systemctl restart systemd-resolvedWireGuard-Tunnel abschalten
Für Updates muss der VPN-Tunnel temporär deaktiviert werden.
PiVPN ausschalten:
pivpn offWireGuard wieder starten:
service wg-quick@wg0 startSSH-Zugang auf den NAT-VPS absichern
Um unbefugten Zugriff auf den VPS zu verhindern, empfiehlt sich ein SSH-Keypair. In diesem Zuge habe ich ein Skript erstellt, das per DNS-Lookup deine DynDNS auflöst und deine aktuelle IP automatisch in iptables hinterlegt.
#!/bin/bash
# Name: dynamic-ssh-access
# Description: Automatically manage iptables rules for SSH access based on DNS
# --------------------------
# Configuration (Edit these)
# --------------------------
DYNAMIC_DNS="your-dynamic-dns.example.com" # Replace with your DNS name
HOSTFILE="/var/lib/ssh-access/current_ips.txt"
SSH_PORT="22"
# --------------------------
# Constants
# --------------------------
IPTABLES="/sbin/iptables"
IP6TABLES="/sbin/ip6tables"
CHAIN_V4="SSH_ACCESS"
CHAIN_V6="SSH_ACCESS6"
# --------------------------
# Functions
# --------------------------
validate_ip() {
local ip=$1
# IPv4 validation
[[ $ip =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]] && return 0
# IPv6 validation
[[ $ip =~ ^([0-9a-fA-F]{0,4}:){1,7}[0-9a-fA-F]{0,4}$ ]] && return 0
return 1
}
resolve_ips() {
# Get IPv4 (A-record)
local ipv4=$(dig +short "$DYNAMIC_DNS" A 2>/dev/null | \
grep -Eo '([0-9]{1,3}\.){3}[0-9]{1,3}' | tail -n1)
# Get IPv6 (AAAA-record)
local ipv6=$(dig +short "$DYNAMIC_DNS" AAAA 2>/dev/null | \
grep -Eo '([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}' | tail -n1)
echo "$ipv4 $ipv6"
}
# --------------------------
# Main Script
# --------------------------
# Resolve current IPs
read -r NEW_IPV4 NEW_IPV6 <<< $(resolve_ips)
# Validate at least one IP exists
if [ -z "$NEW_IPV4" ] && [ -z "$NEW_IPV6" ]; then
echo "Error: No valid IPs found for $DYNAMIC_DNS" >&2
exit 1
fi
# Load previous IPs
declare -A OLD_IPS
if [ -f "$HOSTFILE" ]; then
while IFS= read -r line; do
if [[ $line =~ ^IPv4=(.*) ]]; then
OLD_IPS["v4"]="${BASH_REMATCH[1]}"
elif [[ $line =~ ^IPv6=(.*) ]]; then
OLD_IPS["v6"]="${BASH_REMATCH[1]}"
fi
done < "$HOSTFILE"
fi
# Process IPv4 changes
if [ -n "$NEW_IPV4" ]; then
if ! validate_ip "$NEW_IPV4"; then
echo "Invalid IPv4: $NEW_IPV4" >&2
exit 1
fi
if [ "${OLD_IPS[v4]}" != "$NEW_IPV4" ]; then
echo "Updating IPv4 rules for $NEW_IPV4"
# Create chain if missing
$IPTABLES -N "$CHAIN_V4" 2>/dev/null
# Ensure chain is linked
if ! $IPTABLES -C INPUT -p tcp --dport "$SSH_PORT" -j "$CHAIN_V4" 2>/dev/null; then
$IPTABLES -I INPUT 1 -p tcp --dport "$SSH_PORT" -j "$CHAIN_V4"
fi
# Update rules
$IPTABLES -F "$CHAIN_V4"
$IPTABLES -A "$CHAIN_V4" -s "$NEW_IPV4/32" -p tcp --dport "$SSH_PORT" \
-m conntrack --ctstate NEW,ESTABLISHED -j ACCEPT
$IPTABLES -A "$CHAIN_V4" -p tcp --dport "$SSH_PORT" -j DROP
fi
fi
# Process IPv6 changes
if [ -n "$NEW_IPV6" ]; then
if ! validate_ip "$NEW_IPV6"; then
echo "Invalid IPv6: $NEW_IPV6" >&2
exit 1
fi
if [ "${OLD_IPS[v6]}" != "$NEW_IPV6" ]; then
echo "Updating IPv6 rules for $NEW_IPV6"
# Create chain if missing
$IP6TABLES -N "$CHAIN_V6" 2>/dev/null
# Ensure chain is linked
if ! $IP6TABLES -C INPUT -p tcp --dport "$SSH_PORT" -j "$CHAIN_V6" 2>/dev/null; then
$IP6TABLES -I INPUT 1 -p tcp --dport "$SSH_PORT" -j "$CHAIN_V6"
fi
# Update rules
$IP6TABLES -F "$CHAIN_V6"
$IP6TABLES -A "$CHAIN_V6" -s "$NEW_IPV6/128" -p tcp --dport "$SSH_PORT" \
-m conntrack --ctstate NEW,ESTABLISHED -j ACCEPT
$IP6TABLES -A "$CHAIN_V6" -p tcp --dport "$SSH_PORT" -j DROP
fi
fi
# Store current IPs
mkdir -p "$(dirname "$HOSTFILE")"
> "$HOSTFILE"
[ -n "$NEW_IPV4" ] && echo "IPv4=$NEW_IPV4" >> "$HOSTFILE"
[ -n "$NEW_IPV6" ] && echo "IPv6=$NEW_IPV6" >> "$HOSTFILE"
echo "Rules updated successfully:"
[ -n "$NEW_IPV4" ] && echo "- IPv4: $NEW_IPV4"
[ -n "$NEW_IPV6" ] && echo "- IPv6: $NEW_IPV6"Das Aktualisierungs-Intervall wird per Crontab gesteuert:
# Beispiel-Cronjob (alle 5 Minuten)
*/5 * * * * root /pfad/zum/update-ssh-access.sh