cancel
Showing results for 
Show  only  | Search instead for 
Did you mean: 

Setting up working dual stack (IPv4 and IPv6) wireguard home server on EE

TraderTravel
Established Contributor
Established Contributor

 

Raspberry Pi VPN gateway with dynamic DNS, WireGuard, and NAT66

A reproducible, fully annotated runbook for deploying a dual‑stack VPN gateway on Raspberry Pi OS Trixie with EE broadband, Dynu dynamic DNS, WireGuard, and NAT66. This guide is anonymised so any network savvy EE user can follow it.


1. Overview and rationale

This setup solves two common problems with consumer ISPs and routers: unstable public IPs and limited IPv6 routing support. By combining WireGuard with Dynamic DNS and NAT66, you get a reliable, full‑tunnel experience for both IPv4 and IPv6 across your devices.

  • Why Dynamic DNS: ISPs like EE often change your public IPs. Dynamic DNS keeps a human‑friendly domain pointing to your gateway automatically. Dynu is a good default because it’s free and straightforward, but you can use any provider that supports standard update protocols.

  • Why NAT66: EE’s Smart Hub Pro does not support prefix delegation or proxy NDP for your downstream gateway. That means you can’t route your global /64 directly to WireGuard clients. NAT66 translates ULA IPv6 from clients to the Pi’s global IPv6, making IPv6 work reliably despite these limitations. A full rationale is included later.


2. Prerequisites

Network and hardware

  • EE Broadband Smart Hub Pro (Hub):

    • Reset: Perform a full factory reset.

    • Pair: Re‑pair the Hub to your broadband line.

  • EE Smart Wifi Pro Extenders:

    • Reset: Factory reset each extender.

    • Pair: Re‑pair them to the Hub.

  • EE App:

    • Pair: Link Hub and Extenders to your EE account for stable mesh management before adding services.

Raspberry Pi baseline

  • OS: Raspberry Pi OS Trixie (Debian 13 base), clean install recommended.

  • Connectivity: Ethernet to the EE Hub (we will enable predictable interface names).

  • System updates:

    sudo apt update && sudo apt full-upgrade -y
    sudo reboot

Predictable network interface names

  • Enable via raspi-config:

    • Run sudo raspi-config

    • Advanced Options → Network interface names → Enable “predictable” names

    • Reboot: sudo reboot

  • Verify external interface (typically end0):

    ip link show

Access model

  • Sudo privileges: All commands assume sudo; root login (su) is not required.

Domain and Dynamic DNS

  • Domain: ExamplePersonalDomain.com (replace with your own domain).

  • Dynu account: Free dynamic DNS to keep your domain updated as EE changes your public IPs. Other providers (e.g., Cloudflare API, DuckDNS) can be used if preferred.

WireGuard clients

  • WireGuard installed on your devices (computer, phone, tablet).

  • Keys generated for server and each client.


3. Dynamic DNS with ddclient

Install ddclient

sudo apt install ddclient

Configure ddclient

# ----------------------------------------
# ddclient configuration for Dynu (free)
# ----------------------------------------
# Updates your Dynu domain with current IPv4 and IPv6.
# Ensures clients can always reach your Pi despite changing ISP IPs.
# If using another provider, adapt protocol/server/login/password accordingly.
# ----------------------------------------

protocol=dyndns2
use=web, web=checkip.dynu.com/, web-skip='Current IP Address:'
server=api.dynu.com
login=<dynu-username>
password='<dynu-password>'
ExamplePersonalDomain.com

Enable and verify

sudo systemctl enable ddclient
sudo systemctl restart ddclient
journalctl -u ddclient -f

4. WireGuard server

Install WireGuard

sudo apt install wireguard

Server config

# ----------------------------------------
# WireGuard server configuration
# ----------------------------------------
# - Dual-stack inside the tunnel: IPv4 + ULA IPv6
# - Uses dedicated routing table for clean policy routing
# - PostUp/Down scripts manage forwarding and NAT
# ----------------------------------------

[Interface]
Address = 10.0.0.1/24, fd42:1234:5678::1/64
ListenPort = 51820
PrivateKey = <server-private-key>
Table = 51820
MTU = 1420
PostUp   = /etc/wireguard/wg0-postup.sh
PostDown = /etc/wireguard/wg0-postdown.sh

# ---- Peers ----

[Peer]
# Computer client
PublicKey = <computer-client-public-key>
AllowedIPs = 10.0.0.2/32, fd42:1234:5678::2/128
PersistentKeepalive = 25

[Peer]
# Phone client
PublicKey = <phone-client-public-key>
AllowedIPs = 10.0.0.3/32, fd42:1234:5678::3/128
PersistentKeepalive = 25

5. WireGuard PostUp/PostDown scripts

PostUp: /etc/wireguard/wg0-postup.sh

#!/bin/bash
set -e

# ----------------------------------------
# WireGuard PostUp
# ----------------------------------------
# Enables forwarding and installs IPv4 NAT and IPv6 forwarding.
# Triggers NAT66 refresh to adapt to EE's dynamic global IPv6.
# ----------------------------------------

# Enable forwarding
sysctl -w net.ipv4.ip_forward=1 >/dev/null
sysctl -w net.ipv6.conf.all.forwarding=1 >/dev/null

# IPv4 forwarding + NAT44
iptables -A FORWARD -i wg0 -o end0 -j ACCEPT
iptables -A FORWARD -i end0 -o wg0 -j ACCEPT
iptables -t nat -A POSTROUTING -o end0 -j MASQUERADE

# IPv6 forwarding (NAT66 handled by service)
ip6tables -A FORWARD -i wg0 -o end0 -j ACCEPT
ip6tables -A FORWARD -i end0 -o wg0 -j ACCEPT

# Refresh NAT66 rule for ULA subnet
systemctl start nat66-refresh.service

PostDown: /etc/wireguard/wg0-postdown.sh

#!/bin/bash
set -e

# ----------------------------------------
# WireGuard PostDown
# ----------------------------------------
# Cleans up forwarding and NAT rules for both IPv4 and IPv6.
# ----------------------------------------

# IPv4 cleanup
iptables -D FORWARD -i wg0 -o end0 -j ACCEPT || true
iptables -D FORWARD -i end0 -o wg0 -j ACCEPT || true
iptables -t nat -D POSTROUTING -o end0 -j MASQUERADE || true

# IPv6 cleanup
ip6tables -D FORWARD -i wg0 -o end0 -j ACCEPT || true
ip6tables -D FORWARD -i end0 -o wg0 -j ACCEPT || true
ip6tables -t nat -D POSTROUTING -s fd42:1234:5678::/64 -j MASQUERADE || true

Ensure scripts are executable

sudo chmod +x /etc/wireguard/wg0-postup.sh /etc/wireguard/wg0-postdown.sh
sudo chown root:root /etc/wireguard/wg0-postup.sh /etc/wireguard/wg0-postdown.sh

Enable WireGuard

sudo systemctl enable wg-quick@wg0
sudo systemctl start wg-quick@wg0

6. NAT66 refresh service and timer

NAT66 refresh script

#!/bin/bash
set -e

# ----------------------------------------
# Dynamic NAT66 refresh script
# ----------------------------------------
# Detects the Pi's current global IPv6 on end0 (EE assigns dynamically),
# prefers the /64 address (common on EE), and refreshes the MASQUERADE rule
# for the ULA subnet so clients get stable outbound IPv6.
# ----------------------------------------

# Prefer /64 address, fallback to any global (exclude link-local and temporary)
GLOBAL_IPV6=$(ip -6 addr show dev end0 \
  | awk '/inet6/ && /scope global/ && !/temporary/ {print $2}' \
  | grep -v '^fe80' \
  | grep '/64' \
  | cut -d/ -f1 \
  | head -n1)

if [[ -z "$GLOBAL_IPV6" ]]; then
  GLOBAL_IPV6=$(ip -6 addr show dev end0 \
    | awk '/inet6/ && /scope global/ && !/temporary/ {print $2}' \
    | grep -v '^fe80' \
    | cut -d/ -f1 \
    | head -n1)
fi

if [[ -z "$GLOBAL_IPV6" ]]; then
  echo " No usable global IPv6 found on end0"
  exit 1
fi

echo " Detected global IPv6: $GLOBAL_IPV6"

# Refresh NAT66 rule (remove stale, add fresh)
ip6tables -t nat -D POSTROUTING -s fd42:1234:5678::/64 -j MASQUERADE 2>/dev/null || true
ip6tables -t nat -A POSTROUTING -s fd42:1234:5678::/64 -j MASQUERADE

echo " NAT66 MASQUERADE rule applied for fd42:1234:5678::/64 → $GLOBAL_IPV6"

Make the script executable

sudo chmod +x /usr/local/bin/nat66-refresh.sh
sudo chown root:root /usr/local/bin/nat66-refresh.sh

NAT66 refresh service

[Unit]
Description=Refresh NAT66 ip6tables rule for WireGuard ULA subnet
After=network-online.target

[Service]
Type=oneshot
ExecStart=/usr/local/bin/nat66-refresh.sh
RemainAfterExit=true

[Install]
WantedBy=multi-user.target

NAT66 periodic timer (every 15 minutes)

[Unit]
Description=Periodic NAT66 refresh (every 15 minutes)

[Timer]
OnBootSec=2min
OnUnitActiveSec=15min
Persistent=true

[Install]
WantedBy=timers.target

Enable service and timer

sudo systemctl daemon-reload
sudo systemctl enable nat66-refresh.service
sudo systemctl enable --now nat66-refresh.timer
sudo systemctl start nat66-refresh.service

Verify timer

systemctl list-timers | grep nat66

7. Client configurations (anonymised)

Computer client

# ----------------------------------------
# Computer WireGuard client
# ----------------------------------------
# - ULA IPv6 inside the tunnel; NAT66 on the Pi translates outbound IPv6.
# - Full-tunnel routing for IPv4 and IPv6.
# ----------------------------------------

[Interface]
PrivateKey = <computer-client-private-key>
Address = 10.0.0.2/32, fd42:1234:5678::2/128
DNS = 1.1.1.1, 2606:4700:4700::1111

[Peer]
PublicKey = <server-public-key>
Endpoint = ExamplePersonalDomain.com:51820
AllowedIPs = 0.0.0.0/0, ::/0
PersistentKeepalive = 25

Phone client

# ----------------------------------------
# Phone WireGuard client
# ----------------------------------------
# - ULA IPv6 inside the tunnel; NAT66 on the Pi translates outbound IPv6.
# - Full-tunnel routing for IPv4 and IPv6.
# ----------------------------------------

[Interface]
PrivateKey = <phone-client-private-key>
Address = 10.0.0.3/32, fd42:1234:5678::3/128
DNS = 1.1.1.1, 2606:4700:4700::1111

[Peer]
PublicKey = <server-public-key>
Endpoint = ExamplePersonalDomain.com:51820
AllowedIPs = 0.0.0.0/0, ::/0
PersistentKeepalive = 25

8. Testing and audits

Functional tests

  • WireGuard status on the Pi:

    sudo wg show
  • NAT66 rule present:

    sudo ip6tables -t nat -S | grep MASQUERADE
  • Public IPv6 from client (Computer/Phone):

    curl -6 https://ifconfig.co

    Should match the Pi’s current global IPv6.

  • Browser IPv6 readiness: visit https://test-ipv6.com

Audit checklist

  • Proxy NDP entries (should be empty):

    ip -6 neigh show proxy dev end0
  • IPv6 forwarding enabled:

    sysctl net.ipv6.conf.all.forwarding
    # expect: net.ipv6.conf.all.forwarding = 1
  • Timer active:

    systemctl list-timers | grep nat66

9. Why we’re using Dynu

  • Purpose: Keep ExamplePersonalDomain.com pointing to your gateway even when EE changes your public IPs.

  • Why Dynu:

    • Free plan with core features sufficient for home use.

    • Standard protocols (dyndns2) work with ddclient out‑of‑the‑box.

    • IPv4 and IPv6 updates supported.

  • Alternatives: DuckDNS, Cloudflare API scripts, No‑IP, etc. If you already use a different provider, adapt /etc/ddclient.conf or use their API tool instead.


10. Why NAT66 is necessary on EE

Preferred IPv6 model (no NAT)

  • Delegated prefix: ISP provides a /64 or larger.

  • Direct addressing: Assign global IPv6 to WireGuard clients.

  • Routing announcements: Gateway uses proxy NDP or prefix delegation so the router forwards traffic to those client addresses.

  • Outcome: True end‑to‑end IPv6 without translation.

EE environment limitations

  • Single /64 only: No extra prefixes to route to downstream networks.

  • No prefix delegation: Smart Hub Pro does not delegate a routable prefix to your Pi.

  • Proxy NDP ignored: The router does not honour your gateway’s claims for client addresses.

  • Result: Packets sourced from client‑assigned global IPv6 addresses get dropped.

NAT66 as the practical workaround

  • ULA inside tunnel: Clients use fd42:1234:5678::/64 on WireGuard.

  • Translation at the gateway: Pi uses ip6tables MASQUERADE to translate outbound IPv6 to its own global IPv6 on end0.

  • Router compatibility: The EE router sees traffic as coming from the Pi’s known global IPv6 and forwards it reliably.

  • Outcome: Stable full‑tunnel IPv6 for clients despite consumer router constraints.

Trade‑offs

  • Pros: Works with consumer gear, resilient to ISP address changes, minimal client config.

  • Cons: Loses end‑to‑end global addressing; traffic appears from a single global IPv6 (the gateway’s).

Future path (if supported)

  • If EE (or a different ISP/router) supports prefix delegation or honours proxy NDP, you can assign global IPv6 to clients directly and remove NAT66 for a purist routed configuration.


11. Maintenance tips

  • Keep OS and packages updated:

    sudo apt update && sudo apt full-upgrade -y
  • Monitor ddclient:

    journalctl -u ddclient -f
  • Monitor NAT66 refresh (manual runs):

    sudo /usr/local/bin/nat66-refresh.sh
  • Restart WireGuard after changes:

    sudo systemctl restart wg-quick@wg0
    sudo wg show

1 REPLY 1
TraderTravel
Established Contributor
Established Contributor

To forward port 51820 on the EE Smart Hub Pro for WireGuard or similar services, you'll need to create a custom port forwarding rule and optionally configure firewall pinholes. Here's a step-by-step guide.

 

🛠️ How to Set Up Port Forwarding for Port 51820 on EE Smart Hub Pro

This setup enables external access to your Raspberry Pi or other device running WireGuard via UDP port 51820.

🔐 Prerequisites

  • Your Raspberry Pi or target device must have a static IP address on your LAN (e.g., 192.168.1.100)
  • You must know your EE Smart Hub Pro’s admin password (found on the back of the router)
  • WireGuard must be configured to listen on UDP port 51820

 

⚙️ Step-by-Step Instructions

  1. Access the EE Smart Hub Manager
  2. Navigate to Port Forwarding Settings
    • Click Advanced Settings
    • Select Firewall
    • Choose Create a new port forwarding rule
  3. Create the Port Forwarding Rule
    • Rule name: WireGuard 51820
    • Select device: Choose your Raspberry Pi or manually enter its IP (e.g., 192.168.1.100)
    • External ports: 51820
    • Internal ports: 51820
    • Protocol: UDP
    • Click Set, then Save
  4. (For IPv6 Support) Configure Pinholes
  • If your EE Smart Hub Pro supports pinhole configuration:
  • Go to Firewall > Pinholes
  • Add a new pinhole for the same device and port
  • Use the same port number: 51820
  • Protocol: UDP
  • Save the configuration

 

Verification & Tips

  • Use an external device or online tool like PortChecker to verify that port 51820 is open.
  • Ensure your WireGuard service is actively listening on port 51820 and bound to the correct interface (end0 in your case).
  • If using dynamic DNS (e.g., Dynu via ddclient), confirm that your domain resolves to your public IP.