30-10-2025 04:39 PM
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.
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.
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.
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
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
Sudo privileges: All commands assume sudo; root login (su) is not required.
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 installed on your devices (computer, phone, tablet).
Keys generated for server and each client.
sudo apt install ddclient
File: /etc/ddclient.conf
# ---------------------------------------- # 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
sudo systemctl enable ddclient sudo systemctl restart ddclient journalctl -u ddclient -f
sudo apt install wireguard
File: /etc/wireguard/wg0.conf
# ---------------------------------------- # 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
#!/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
#!/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
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
sudo systemctl enable wg-quick@wg0 sudo systemctl start wg-quick@wg0
File: /usr/local/bin/nat66-refresh.sh
#!/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"sudo chmod +x /usr/local/bin/nat66-refresh.sh sudo chown root:root /usr/local/bin/nat66-refresh.sh
File: /etc/systemd/system/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
File: /etc/systemd/system/nat66-refresh.timer
[Unit] Description=Periodic NAT66 refresh (every 15 minutes) [Timer] OnBootSec=2min OnUnitActiveSec=15min Persistent=true [Install] WantedBy=timers.target
sudo systemctl daemon-reload sudo systemctl enable nat66-refresh.service sudo systemctl enable --now nat66-refresh.timer sudo systemctl start nat66-refresh.service
systemctl list-timers | grep nat66
# ---------------------------------------- # 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 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
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
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
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.
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.
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.
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.
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).
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.
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
30-10-2025 04:45 PM
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
⚙️ Step-by-Step Instructions
✅ Verification & Tips