How to Route a VLAN Through a WireGuard VPN Exit Gateway with OpenWrt, AdGuard Home, and Azure

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.1 and tls://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:

  1. Sysctl: net.ipv6.conf.br-lan/9.disable_ipv6=1
  2. DHCP: RA disabled, DHCPv6 disabled on VPN interface
  3. Firewall: DROP all IPv6 from VPN zone
  4. 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)

  • fq qdisc 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 Opennet.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 nat and ack-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

LayerWhat
TunnelWireGuard, MTU 1420, BBR, 25s keepalive
RoutingVLAN 9 → policy route table 2 → wireguardgig
Kill switchvpn → wanguard only, no vpn → wan forwarding
DNSAdGuard Home on both ends, Cloudflare DoT upstream
DNS leak preventionDHCP option 6 + DNAT intercept for hardcoded DNS
IPv6 leak preventionDisabled at sysctl, DHCP, RA, and firewall
Ad blocking~173K rules (AdGuard DNS + AdAway + Peter Lowe)
PerformanceCAKE SQM, RPS, fq qdisc, tuned TCP buffers
AccessDedicated 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.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.