Technical reference for establishing a full-tunnel VPN exit using an OpenWrt router as client and a Debian Trixie VPS as server.
Overview
• Router: OpenWrt (tested on 25.12.0-rc4, ramips/mt7621)
• Server: Debian 13 (Trixie) VPS
• Tunnel network: 10.0.100.0/24
• Router tunnel IP: 10.0.100.2/32
• Server tunnel IP: 10.0.100.1/24
• WireGuard port: 51820/udp
Server Setup (Trixie)
Install WireGuard
Code: Select all
sudo apt update && sudo apt install wireguardCode: Select all
echo "net.ipv4.ip_forward=1" | sudo tee /etc/sysctl.conf
sudo /sbin/sysctl -pCode: Select all
cat /proc/sys/net/ipv4/ip_forward
# Should return: 1Code: Select all
wg genkey | tee /tmp/server_private | wg pubkey > /tmp/server_public
cat /tmp/server_private # <SERVER_PRIVATE_KEY> - keep secret
cat /tmp/server_public
# <SERVER_PUBLIC_KEY> - give to router configOn the router (or any machine with wg installed):
Code: Select all
wg genkey | tee /tmp/router_private | wg pubkey > /tmp/router_public
cat /tmp/router_private # <ROUTER_PRIVATE_KEY> - keep secret
cat /tmp/router_public
# <ROUTER_PUBLIC_KEY> - give to server configCreate Server Configuration
Replace <SERVER_PRIVATE_KEY> and <ROUTER_PUBLIC_KEY> with actual values:
Code: Select all
sudo tee /etc/wireguard/wg0.conf << 'EOF'
[Interface]
Address = 10.0.100.1/24
ListenPort = 51820
PrivateKey = <SERVER_PRIVATE_KEY>
PostUp = iptables -t nat -A POSTROUTING -s 10.0.100.0/24 -o eth0 -j MASQUERADE
PostDown = iptables -t nat -D POSTROUTING -s 10.0.100.0/24 -o eth0 -j MASQUERADE
[Peer]
PublicKey = <ROUTER_PUBLIC_KEY>
AllowedIPs = 10.0.100.2/32
EOFConfigure UFW Firewall
Code: Select all
sudo ufw allow 51820/udp
sudo ufw route allow in on wg0 out on eth0
sudo ufw route allow in on eth0 out on wg0
Start WireGuard
sudo systemctl enable wg-quick@wg0
sudo systemctl start wg-quick@wg0
sudo wg showConfigure WireGuard Interface via UCI
Replace placeholders with actual values:
Code: Select all
# Create interface
uci set network.wgserver=interface
uci set network.wgserver.proto='wireguard'
uci set network.wgserver.private_key='<ROUTER_PRIVATE_KEY>'
uci set network.wgserver.addresses='10.0.100.2/32'
# Add peer (the server)
uci add network wireguard_wgserver
uci set network.@wireguard_wgserver[-1].public_key='<SERVER_PUBLIC_KEY>'
uci set network.@wireguard_wgserver[-1].endpoint='<SERVER_IP>:51820'
uci set network.@wireguard_wgserver[-1].allowed_ips='0.0.0.0/0'
uci set network.@wireguard_wgserver[-1].persistent_keepalive='25'
uci commit networkAdd the WireGuard interface to the wan zone for masquerading and forwarding:
Code: Select all
uci add_list firewall.@zone[1].network='wgserver'
uci commit firewall
/etc/init.d/firewall restartCode: Select all
ifdown wgserver && ifup wgserver
wg show wgserverCreate /root/vpn-toggle.sh:
Code: Select all
#!/bin/sh
WG_INTERFACE="wgserver"
WG_ENDPOINT="<SERVER_IP>"
WG_PUBKEY="<SERVER_PUBLIC_KEY>"
WAN_GATEWAY="192.168.1.254" # Adjust to your upstream gateway
case "$1" in
on)
# Ensure endpoint route exists (critical!)
ip route add $WG_ENDPOINT via $WAN_GATEWAY dev wan 2>/dev/null
# Set endpoint if missing (workaround for netifd bug)
wg set $WG_INTERFACE peer $WG_PUBKEY endpoint $WG_ENDPOINT:51820 2>/
dev/null
ip route replace default dev $WG_INTERFACE
logger -t vpn-toggle "VPN enabled"
;;
off)
ip route replace default via $WAN_GATEWAY dev wan
logger -t vpn-toggle "VPN disabled"
;;
status)
if ip route | grep -q "default dev $WG_INTERFACE"; then
echo "VPN: active"
curl -s ipinfo.io | grep -E '"(ip|city|country)"'
else
echo "VPN: inactive"
fi
;;
*)
echo "Usage: $0 {on|off|status}"
;;
esac
chmod +x /root/vpn-toggle.shEndpoint Route
When replacing the default route to go through the WireGuard tunnel, the router must still
be able to reach the VPN server’s public IP via the normal WAN gateway. Without this,
encapsulated packets have nowhere to go:
ip route add <SERVER_IP> via <WAN_GATEWAY> dev wan
The toggle script handles this automatically.
Testing
From router:
Code: Select all
./vpn-toggle.sh on
curl -s ipinfo.io # Should show server location
./vpn-toggle.sh offCode: Select all
curl -s ipinfo.io
# Should match router's exitwg show wgserver
Look for: - endpoint: showing server IP and port - latest handshake: within last 2 minutes - transfer: showing bytes in both directions
Troubleshooting
No handshake
• Check server firewall allows 51820/udp
• Check endpoint is set: wg show wgserver
• Manually set endpoint if missing (see bug below)
Handshake but no traffic
• Check server IP forwarding: cat /proc/sys/net/ipv4/ip_forward
• Check server NAT rule: sudo iptables -t nat -L POSTROUTING -v
• Check UFW forwarding rules
Traffic sent but nothing received
• Missing endpoint route - packets can’t reach server
• Server not masquerading - check iptables rule
Preserve Across Sysupgrade
Add to /etc/sysupgrade.conf:
Code: Select all
/root/