> ## Content Index
> Fetch the complete content index at: https://blog.woerner-it.eu/llms.txt
> Use this file to discover other available public pages before exploring further.

# Feste IPv4 trotz CGNAT: NAT-Bridge über einen günstigen VPS mit WireGuard
- URL: https://blog.woerner-it.eu/cgnat-vps-nat/
- Published: 2026-07-16T18:26:41.000Z
- Updated: 2026-07-16T18:43:40.000Z
- Description: 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.
- Author: Jonas Wörner
- Tags: Selfhosting, Netzwerk, WireGuard

## 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 €)](https://www.ionos.de/server/vps?ref=blog.woerner-it.eu) eine NAT-Bridge und leitest jeglichen IPv4-Traffic über diesen Server weiter. Alternativ bekommst du über [Oracle Free Tier (AMD Compute Instance)](https://docs.oracle.com/de-de/iaas/Content/FreeTier/freetier%5Ftopic-Always%5FFree%5FResources.htm?ref=blog.woerner-it.eu#compute) 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:

```bash
apt-get update && apt-get upgrade -y
```

PiVPN installieren:

```bash
curl -L https://install.pivpn.io | bash
```

Namen des Netzwerk-Interfaces herausfinden:

```bash
ip a
```

Die WireGuard-Konfiguration bearbeiten:

```bash
nano /etc/wireguard/wg0.conf
```

Direkt nach der Zeile **ListenPort** folgende Zeile einfügen (bei **dein\_interface** den zuvor ermittelten Interface-Namen eintragen):

```bash
PostUp = iptables -A FORWARD -i %i -j ACCEPT; iptables -t nat -A POSTROUTING -o dein_interface -j MASQUERADE
```

Danach das System neu starten und ein Client-Profil anlegen:

```bash
pivpn -a
```

Einen Namen für das Profil vergeben, z. B. **heimnetzwerk**.

Die Konfiguration erneut öffnen:

```bash
nano /etc/wireguard/wg0.conf
```

Bei **AllowedIPs** den IP-Bereich deines Heimnetzwerks ergänzen:

```ini
AllowedIPs = 10.70.193.2/32, 192.168.8.0/24
```

Den Inhalt der erzeugten Client-Konfiguration vorübergehend zwischenspeichern (wird später auf der VM gebraucht):

```bash
cat /etc/wireguard/configs/heimnetzwerk.conf
```

IP-Forwarding aktivieren – dazu die sysctl-Konfiguration bearbeiten:

```bash
nano /etc/sysctl.conf
```

Die Zeile **#net.ipv4.ip\_forward = 1** auskommentieren, sodass daraus **net.ipv4.ip\_forward = 1** wird.

Ein Skript für die NAT-Regeln anlegen:

```bash
nano /ip.sh
```

Folgenden Inhalt eintragen:

```bash
#!/bin/bash
iptables -t nat -A POSTROUTING -j MASQUERADE
```

Um **alle Ports** an 192.168.8.14 weiterzuleiten (in diesem Fall der Nginx Proxy Manager):

```bash
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.14
```

Um nur einen **einzelnen Port (80)** an 192.168.8.14 weiterzuleiten (in diesem Fall ein Webserver):

```bash
iptables -t nat -A PREROUTING -p "tcp" --dport "80" -j DNAT --to-destination "192.168.8.14":"80"
```

iptables-Regeln dauerhaft speichern:

```bash
sudo apt install netfilter-persistent
sudo netfilter-persistent save
sudo systemctl enable netfilter-persistent
```

Das Skript ausführbar machen:

```bash
chmod +x /ip.sh
```

Crontab öffnen:

```bash
crontab -e
```

Und folgende Zeile eintragen, damit die Regeln bei jedem Neustart gesetzt werden:

```bash
@reboot /ip.sh
```

Nach einem finalen Reboot ist die Einrichtung auf dem VPS **abgeschlossen**.

## Einrichtung auf der VM (Heimnetz)

System aktualisieren und WireGuard installieren:

```bash
apt-get update && apt-get upgrade -y && apt-get install -y wireguard openresolv wireguard-dkms
```

Die WireGuard-Konfiguration anlegen und den Inhalt aus **/etc/wireguard/configs/heimnetzwerk.conf** (vom VPS) einfügen:

```bash
nano /etc/wireguard/wg0.conf
```

Vor dem Abschnitt **\[Peer\]** folgende Zeilen einfügen und das Interface anpassen (bei **dein\_interface**):

```bash
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_forward
```

Am Ende der Datei außerdem folgende Zeile ergänzen, damit der Tunnel dauerhaft offen bleibt:

```ini
PersistentKeepalive = 25
```

WireGuard aktivieren:

```bash
systemctl enable wg-quick@wg0
```

Nach 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.

![](https://blog.woerner-it.eu/content/images/2026/07/ionos-firewall-1-1.png)

![](https://blog.woerner-it.eu/content/images/2026/07/ionos-firewall-2.png)

## Optionale Anpassungen

### DNS für den WireGuard-Tunnel aktivieren

DNS in **/etc/wireguard/wg0.conf** hinterlegen:

```ini
DNS = 9.9.9.9
```

Anschließend die Datei **/etc/systemd/resolved.conf.d/disable-stub.conf** mit folgendem Inhalt anlegen:

```ini
[Resolve]
DNSStubListener=no
```

Danach systemd-resolved neu starten:

```bash
sudo systemctl restart systemd-resolved
```

### WireGuard-Tunnel abschalten

**Für Updates muss der VPN-Tunnel temporär deaktiviert werden.**

PiVPN ausschalten:

```bash
pivpn off
```

WireGuard wieder starten:

```bash
service wg-quick@wg0 start
```

### SSH-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.

```bash
#!/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:

```bash
# Beispiel-Cronjob (alle 5 Minuten)
*/5 * * * * root /pfad/zum/update-ssh-access.sh
```