The Goal
Route an entire VLAN through a WireGuard tunnel to an Azure VM, so every device on that network exits the internet from a cloud IP — with ad blocking, DNS encryption, and a kill switch. No split tunneling, no DNS leaks, no IPv6 leaks.
The result: connect to a dedicated “vpn” WiFi SSID at home, and all your traffic exits through Azure. Disconnect, and you are back on your normal internet. Simple for the user, robust underneath.
Architecture
Devices on VLAN 9 ("vpn" WiFi)
↓ (policy routing)
OpenWrt router (gatekeeper) ──WireGuard──→ Azure VM (gig)
↓ ↓
AdGuard Home (LAN DNS) AdGuard Home (VPN DNS)
Cloudflare DoT upstream Cloudflare DoT upstream
Key components:
- gatekeeper — OpenWrt 24.10 router (x86, i7, 16GB RAM) handling all VLANs, firewall, and policy routing
- gig — Azure Ubuntu 24.04 VM acting as the WireGuard exit node
- VLAN 9 — Dedicated VPN network (10.0.9.0/24) with its own WiFi SSID, DHCP, and firewall zone
- AdGuard Home — Network-wide ad blocking on both ends, with encrypted DNS upstream
Step 1: WireGuard Tunnel
Azure VM (gig)
Install WireGuard and enable IP forwarding:
sudo apt install wireguard sudo sysctl -w net.ipv4.ip_forward=1 echo "net.ipv4.ip_forward=1" | sudo tee -a /etc/sysctl.conf
Generate keys and create /etc/wireguard/wg0.conf:
[Interface] Address = 10.5.34.1/24 ListenPort = 51820 PrivateKey = <server-private-key> PostUp = iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE PostDown = iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE [Peer] # gatekeeper PublicKey = <gatekeeper-public-key> AllowedIPs = 10.5.34.10/32
Enable and start:
sudo systemctl enable wg-quick@wg0 sudo systemctl start wg-quick@wg0
Open UDP port 51820 in the Azure Network Security Group and in UFW:
sudo ufw allow 51820/udp sudo ufw allow in on wg0 from 10.5.34.0/24
OpenWrt Router (gatekeeper)
Install WireGuard:
opkg update opkg install wireguard-tools kmod-wireguard
Configure the interface via UCI:
uci set network.wireguardgig=interface uci set network.wireguardgig.proto=wireguard uci set network.wireguardgig.private_key=<gatekeeper-private-key> uci add_list network.wireguardgig.addresses=10.5.34.10/24 uci set network.wireguardgig.mtu=1420 uci set network.wireguardgig.metric=90 uci add network wireguard_wireguardgig uci set network.@wireguard_wireguardgig[-1].public_key=<server-public-key> uci set network.@wireguard_wireguardgig[-1].endpoint_host=<your-vm-hostname> uci set network.@wireguard_wireguardgig[-1].endpoint_port=51820 uci set network.@wireguard_wireguardgig[-1].persistent_keepalive=25 uci add_list network.@wireguard_wireguardgig[-1].allowed_ips=0.0.0.0/0 uci commit network /etc/init.d/network restart
Important: Add a static route to pin the WireGuard endpoint through your real WAN, so the tunnel does not try to route itself through itself:
uci add network route uci set network.@route[-1].interface=wan uci set network.@route[-1].target=<azure-vm-public-ip>/32 uci set network.@route[-1].gateway=<your-wan-gateway> uci commit network
Step 2: VLAN 9 and Policy Routing
Create a dedicated VPN network on VLAN 9. On OpenWrt:
# Bridge and VLAN interface uci set network.vpn=interface uci set network.vpn.proto=static uci set network.vpn.ipaddr=10.0.9.1 uci set network.vpn.netmask=255.255.255.0 uci set network.vpn.device=br-lan.9
Set up policy routing so VLAN 9 traffic uses the WireGuard tunnel instead of the normal WAN:
# Default route through tunnel in table 2 uci add network route uci set network.@route[-1].interface=wireguardgig uci set network.@route[-1].target=0.0.0.0/0 uci set network.@route[-1].table=2 # Rule: traffic from VPN zone uses table 2 uci add network rule uci set network.@rule[-1].src=10.0.9.0/24 uci set network.@rule[-1].lookup=2 uci commit network /etc/init.d/network restart
Devices on VLAN 9 now exit through Azure. Devices on every other VLAN are unaffected.
Step 3: Firewall — Kill Switch and Leak Prevention
The firewall is critical. If the tunnel drops, VPN traffic must NOT fall back to the regular WAN.
# VPN zone — devices on VLAN 9 uci add firewall zone uci set firewall.@zone[-1].name=vpn uci set firewall.@zone[-1].input=REJECT uci set firewall.@zone[-1].output=ACCEPT uci set firewall.@zone[-1].forward=REJECT uci add_list firewall.@zone[-1].network=vpn # WAN guard zone — the WireGuard tunnel exit uci add firewall zone uci set firewall.@zone[-1].name=wanguard uci set firewall.@zone[-1].input=REJECT uci set firewall.@zone[-1].output=ACCEPT uci set firewall.@zone[-1].forward=REJECT uci add_list firewall.@zone[-1].network=wireguardgig uci set firewall.@zone[-1].masq=1 uci set firewall.@zone[-1].mtu_fix=1 # Only forwarding: vpn → wanguard (kill switch) uci add firewall forwarding uci set firewall.@forwarding[-1].src=vpn uci set firewall.@forwarding[-1].dest=wanguard # Allow DNS and DHCP into VPN zone # ... (DNS on port 53, DHCP on port 67) # Drop all IPv6 from VPN zone uci add firewall rule uci set firewall.@rule[-1].name=VPN-Drop-IPv6 uci set firewall.@rule[-1].src=vpn uci set firewall.@rule[-1].family=ipv6 uci set firewall.@rule[-1].proto=all uci set firewall.@rule[-1].target=DROP uci commit firewall /etc/init.d/firewall restart
There is no vpn → wan forwarding. If the tunnel is down, traffic is dropped — not leaked.
Step 4: DNS — No Leaks, With Ad Blocking
DNS is the most common VPN leak. We handle it in layers.
AdGuard Home on gig (VPN DNS)
Install AdGuard Home on the Azure VM, listening only on the WireGuard address:
curl -s -S -L https://raw.githubusercontent.com/AdguardTeam/AdGuardHome/master/scripts/install.sh | sh
Configure via the web UI at http://10.5.34.1:3000:
- DNS listen address:
10.5.34.1:53 - Upstream DNS:
tls://1.1.1.1andtls://1.0.0.1(Cloudflare DNS-over-TLS) - Enable DNSSEC and optimistic caching
- Enable the AdGuard DNS filter (~163K rules), AdAway, and Peter Lowe blocklists
Both the web UI and DNS are bound to the WireGuard interface only — not accessible from the public internet.
AdGuard Home on gatekeeper (LAN DNS)
Install from opkg and configure to listen on 127.0.0.1:5353, then point dnsmasq at it:
opkg install adguardhome # Point dnsmasq at AdGuard Home instead of Stubby uci add_list dhcp.@dnsmasq[0].server='127.0.0.1#5353' uci set dhcp.@dnsmasq[0].noresolv=1 uci commit dhcp /etc/init.d/dnsmasq restart
This replaces Stubby — AdGuard Home handles DNS-over-TLS upstream natively. The DNS chain becomes: dnsmasq (:53) → AdGuard Home (:5353) → Cloudflare DoT.
DHCP: Force VPN Clients to Use Tunnel DNS
uci set dhcp.vpn.dhcp_option='6,10.5.34.1' uci commit dhcp
VPN clients receive 10.5.34.1 as their DNS server. Since this IP is on the WireGuard subnet, all DNS queries go through the tunnel to AdGuard Home on gig.
DNS Intercept: Catch Hardcoded DNS
Some devices (Chromecast, Android) ignore DHCP and hardcode DNS servers like 8.8.8.8. A firewall DNAT redirect catches them:
uci add firewall redirect uci set firewall.@redirect[-1].target=DNAT uci set firewall.@redirect[-1].name='Intercept DNS VPN' uci set firewall.@redirect[-1].src=vpn uci set firewall.@redirect[-1].src_dport=53 uci set firewall.@redirect[-1].dest=wanguard uci set firewall.@redirect[-1].dest_ip=10.5.34.1 uci set firewall.@redirect[-1].dest_port=53 uci set firewall.@redirect[-1].proto='tcp udp' uci commit firewall /etc/init.d/firewall restart
Any DNS query leaving the VPN zone gets redirected to AdGuard Home on gig, through the tunnel. No exceptions.
IPv6 Leak Prevention
IPv6 can leak your real address even when IPv4 is tunneled. Disable it at four layers:
- Sysctl:
net.ipv6.conf.br-lan/9.disable_ipv6=1 - DHCP: RA disabled, DHCPv6 disabled on VPN interface
- Firewall: DROP all IPv6 from VPN zone
- DHCP options: No IPv6 DNS servers advertised
Step 5: Performance Tuning
Out of the box, the tunnel achieved ~305/332 Mbps (up/down). After tuning: ~390/453 Mbps.
Both Sides
- BBR congestion control — Replaces CUBIC. Better throughput on high-latency links.
net.ipv4.tcp_congestion_control=bbr - TCP buffer tuning — Scale rmem_max/wmem_max for the bandwidth-delay product
- ECN — Reduces retransmits.
net.ipv4.tcp_ecn=1 - MTU 1420 — Standard for WireGuard over Ethernet
Azure VM (gig)
fqqdisc on eth0 and wg0 — BBR needs a pacing-aware qdisc. Without it, BBR falls back to burst-based sending which causes buffer bloat- TCP Fast Open —
net.ipv4.tcp_fastopen=3 - 26MB socket buffers — Sized for the bandwidth-delay product of a ~12ms RTT gigabit link
OpenWrt Router (gatekeeper)
- CAKE SQM on the WAN link — 500/500 Mbps with
natandack-filter. Prevents buffer bloat - CAKE SQM on wireguardgig — Per-flow fairness inside the tunnel. Prevents one bulk download from starving interactive traffic
- RPS (Receive Packet Steering) — Spread softirq across all CPU cores. Persistent via
/etc/hotplug.d/net/99-rps
Step 6: Dedicated VPN WiFi SSID
On an OpenWrt access point, create a 5GHz SSID that bridges to VLAN 9:
uci set wireless.vpn_5g=wifi-iface uci set wireless.vpn_5g.device=radio2 uci set wireless.vpn_5g.mode=ap uci set wireless.vpn_5g.ssid=vpn uci set wireless.vpn_5g.encryption=psk2 uci set wireless.vpn_5g.key=<your-password> uci set wireless.vpn_5g.network=vpn uci commit wireless wifi reload
The AP bridges the “vpn” SSID to VLAN 9. Connect to this SSID and all traffic routes through Azure.
Verifying It Works
From a device on the VPN WiFi:
# Check your exit IP — should be the Azure VM curl ifconfig.me # Check DNS — ad domains should be blocked nslookup ads.google.com # Expected: 127.0.0.1 # Check for DNS leaks at https://www.dnsleaktest.com # Should show Cloudflare, not your ISP # Check for IPv6 leaks at https://ipv6leak.com # Should show no IPv6 connectivity
Easy Ad Blocking Toggle
AdGuard Home includes a web dashboard with a full query log. You can:
- One-click whitelist any blocked domain from the query log
- Disable protection globally for 10 seconds, 30 seconds, 1 minute, etc.
- Per-client rules for different filtering per device
Access the dashboards at:
- LAN:
http://<router-ip>:3000 - VPN:
http://10.5.34.1:3000(through the tunnel)
For even quicker toggling, the AdGuard Browser Extension lets you disable filtering per-site from the toolbar.
Summary
| Layer | What |
|---|---|
| Tunnel | WireGuard, MTU 1420, BBR, 25s keepalive |
| Routing | VLAN 9 → policy route table 2 → wireguardgig |
| Kill switch | vpn → wanguard only, no vpn → wan forwarding |
| DNS | AdGuard Home on both ends, Cloudflare DoT upstream |
| DNS leak prevention | DHCP option 6 + DNAT intercept for hardcoded DNS |
| IPv6 leak prevention | Disabled at sysctl, DHCP, RA, and firewall |
| Ad blocking | ~173K rules (AdGuard DNS + AdAway + Peter Lowe) |
| Performance | CAKE SQM, RPS, fq qdisc, tuned TCP buffers |
| Access | Dedicated 5GHz WiFi SSID — connect and go |
The whole setup was built and tuned in a single session with Claude Code handling the configuration across both hosts simultaneously — SSH into the router and VM, running commands, testing, and fixing issues in real time.
