WireGuard Exit Node Deployment¶
Multi-worker WireGuard tunnel infrastructure with VPN exit routing through OpenWRT workers.
Overview¶
This system creates WireGuard tunnels from a central host to 15 OpenWRT workers, each providing a unique exit IP through their VPN connections. Applications can bind to specific tunnel interfaces to route traffic through different geographic exit points.
Architecture¶
flowchart TB
subgraph Host["Host Server (Ubuntu)"]
direction TB
APP[Application]
WG01[wg01<br/>10.200.1.1]
WG02[wg02<br/>10.200.2.1]
WG03[wg03<br/>10.200.3.1]
WGDOTS[...]
WG15[wg15<br/>10.200.15.1]
end
subgraph Workers["OpenWRT Workers (17.0.0.x)"]
direction TB
W01[Worker 01<br/>wg_exit 10.200.1.2]
W02[Worker 02<br/>wg_exit 10.200.2.2]
W03[Worker 03<br/>wg_exit 10.200.3.2]
WDOTS[...]
W15[Worker 15<br/>wg_exit 10.200.15.2]
end
subgraph VPN["VPN Tunnels (tun0)"]
direction TB
V01[Exit: 146.70.x.x]
V02[Exit: 151.243.x.x]
V03[Exit: 149.22.x.x]
VDOTS[...]
V15[Exit: 154.47.x.x]
end
APP --> WG01 & WG02 & WG03 & WG15
WG01 <-->|WireGuard| W01
WG02 <-->|WireGuard| W02
WG03 <-->|WireGuard| W03
WG15 <-->|WireGuard| W15
W01 -->|table 100| V01
W02 -->|table 100| V02
W03 -->|table 100| V03
W15 -->|table 100| V15
Traffic Flow¶
sequenceDiagram
participant App as Application
participant Host as Host (wgXX)
participant Worker as Worker (wg_exit)
participant Table as Policy Route<br/>Table 100
participant VPN as OpenVPN (tun0)
participant Net as Internet
App->>Host: curl --interface wg03
Note over Host: Source: 10.200.3.1<br/>Route via table 203
Host->>Worker: WireGuard encrypted
Worker->>Table: src 10.200.3.0/24
Table->>VPN: default via 10.96.0.1
VPN->>Net: Exit via VPN IP
Net-->>VPN: Response
VPN-->>Worker: Decrypt
Note over Worker: Masquerade (srcnat)
Worker-->>Host: WireGuard encrypted
Host-->>App: Response
Network Configuration¶
Addressing Scheme¶
| Worker | Host Interface | Host IP | Worker IP | Host Port | Worker Port | Routing Table |
|---|---|---|---|---|---|---|
| 01 | wg01 | 10.200.1.1/24 | 10.200.1.2/24 | 51801 | 54501 | 201 |
| 02 | wg02 | 10.200.2.1/24 | 10.200.2.2/24 | 51802 | 54502 | 202 |
| 03 | wg03 | 10.200.3.1/24 | 10.200.3.2/24 | 51803 | 54503 | 203 |
| ... | ... | ... | ... | ... | ... | ... |
| 15 | wg15 | 10.200.15.1/24 | 10.200.15.2/24 | 51815 | 54515 | 215 |
Worker List¶
Workers are defined in scripts/wireguard/workers.txt:
# Format: worker_num,hostname,ip
01,lazarus-worker-01,17.0.0.10
02,lazarus-worker-02,17.0.0.11
03,lazarus-worker-03,17.0.0.12
...
15,lazarus-worker-15,17.0.0.25
Deployment¶
Batch Deployment Script¶
flowchart TD
subgraph Deploy["wg-batch-deploy.sh deploy"]
D1[Parse workers.txt] --> D2[For each worker]
D2 --> D3[Generate host keys]
D3 --> D4[SSH to worker]
D4 --> D5[Install WireGuard if needed]
D5 --> D6[Generate worker keys]
D6 --> D7[Configure UCI network]
D7 --> D8[Configure UCI firewall]
D8 --> D9[Add policy routes]
D9 --> D10[Restart network/firewall]
D10 --> D11[Return worker pubkey]
D11 --> D12[Create host wgXX.conf]
D12 --> D13[wg-quick up wgXX]
D13 --> D14[Test tunnel]
D14 --> D2
end
Commands¶
# Deploy to all workers
./scripts/wireguard/wg-batch-deploy.sh deploy
# Deploy to a single worker
./scripts/wireguard/wg-batch-deploy.sh single 03
# Test all tunnels
./scripts/wireguard/wg-batch-deploy.sh test
# Show status
./scripts/wireguard/wg-batch-deploy.sh status
# Cleanup all tunnels
./scripts/wireguard/wg-batch-deploy.sh cleanup
Script Files¶
| Script | Purpose |
|---|---|
wg-batch-deploy.sh |
Main batch deployment orchestrator |
wg-exit-deploy.sh |
Single worker deployment (legacy) |
fix-routing.sh |
Fix routing table 100 on workers |
workers.txt |
Worker definitions |
Configuration Details¶
Host Configuration¶
Each tunnel creates a host config at /etc/wireguard/wgXX.conf:
# /etc/wireguard/wg03.conf
[Interface]
PrivateKey = <host_private_key>
Address = 10.200.3.1/24
ListenPort = 51803
Table = 203
PostUp = ip rule add from 10.200.3.1 table 203 priority 100
PostDown = ip rule del from 10.200.3.1 table 203 priority 100
[Peer]
# lazarus-worker-03 at 17.0.0.12
PublicKey = <worker_public_key>
AllowedIPs = 0.0.0.0/0
Endpoint = 17.0.0.12:54503
PersistentKeepalive = 25
Worker UCI Configuration¶
flowchart TD
subgraph Network["network config"]
N1[wg_exit interface<br/>proto: wireguard<br/>listen_port: 545XX<br/>addresses: 10.200.X.2/24]
N2[wg_exit_peer<br/>public_key: host_key<br/>endpoint: 17.0.0.240:518XX<br/>allowed_ips: 10.200.X.1/32]
N3[wg_rule_XX<br/>src: 10.200.X.0/24<br/>lookup: 100]
N4[wg_tunnel_route_XX<br/>interface: wg_exit<br/>target: 10.200.X.0/24<br/>table: 100]
N5[wg_default_route_XX<br/>interface: vpn<br/>target: 0.0.0.0<br/>gateway: 10.96.0.1<br/>table: 100]
end
subgraph Firewall["firewall config"]
F1[wg_zone_XX<br/>name: wireguard_XX<br/>input/output/forward: ACCEPT<br/>network: wg_exit<br/>masq: 1]
F2[wg_fwd_XX<br/>src: wireguard_XX<br/>dest: wan]
F3[wg_fwd_vpn_XX<br/>src: wireguard_XX<br/>dest: vpn]
end
N1 --> N2
N3 --> N4 --> N5
F1 --> F2 & F3
Policy Routing¶
Traffic from WireGuard is routed through the worker's VPN (tun0):
flowchart LR
subgraph Worker["OpenWRT Worker"]
WG[wg_exit<br/>10.200.X.2] --> RULE[ip rule:<br/>from 10.200.X.0/24<br/>lookup 100]
RULE --> T100[Table 100:<br/>default via 10.96.0.1<br/>dev tun0]
T100 --> TUN[tun0<br/>VPN Tunnel]
TUN --> MASQ[Masquerade<br/>srcnat_vpn]
end
MASQ --> EXIT[VPN Exit IP]
Usage¶
Bind Traffic to Specific Tunnel¶
# Using curl
curl --interface wg03 https://example.com
curl --interface wg03 https://ipinfo.io/ip
# Using wget
wget --bind-address=10.200.3.1 https://example.com
# In Python (requests)
import requests
session = requests.Session()
session.get('https://example.com',
headers={'Host': 'example.com'},
timeout=30,
stream=True)
# Note: Use socket binding for interface selection
Verify Tunnel¶
# Check WireGuard status
sudo wg show wg03
# Ping tunnel endpoint
ping 10.200.3.2
# Check exit IP
curl --interface wg03 -sSL https://ipinfo.io/ip
Test All Tunnels¶
Expected output:
=== Testing all tunnels ===
wg01 (lazarus-worker-01): OK - Exit IP: 146.70.195.107
wg02 (lazarus-worker-02): OK - Exit IP: 151.243.141.134
wg03 (lazarus-worker-03): OK - Exit IP: 149.22.80.96
...
Troubleshooting¶
Check Handshake¶
No Handshake¶
flowchart TD
A[No handshake] --> B{Can ping worker LAN IP?}
B -->|No| C[Check network connectivity<br/>ping 17.0.0.12]
B -->|Yes| D{Worker WG listening?}
D -->|No| E[Check worker:<br/>ssh root@17.0.0.12<br/>wg show wg_exit]
D -->|Yes| F{Firewall blocking?}
F -->|Yes| G[Check worker firewall:<br/>nft list chain inet fw4 input]
F -->|No| H[Check keys match]
Tunnel Ping Works, HTTP Fails¶
-
Check policy routing on worker:
-
Check firewall forwarding:
-
Fix routing if needed:
Exit IP Shows Main Host IP (104.11.127.142)¶
Traffic is not routing through VPN. Check:
-
Table 100 routes through tun0 (not phy0-sta0):
-
Fix if routing through WAN:
Worker VPN Down¶
# Check if VPN is connected
ssh root@17.0.0.12 "ping -c 1 10.96.0.1"
# Check VPN interface
ssh root@17.0.0.12 "ip link show tun0"
# Restart VPN
ssh root@17.0.0.12 "/etc/init.d/openvpn restart"
Current Deployment Status¶
| Interface | Worker | LAN IP | Exit IP | Status |
|---|---|---|---|---|
| wg01 | lazarus-worker-01 | 17.0.0.10 | 146.70.195.107 | Active |
| wg02 | lazarus-worker-02 | 17.0.0.11 | 151.243.141.134 | Active |
| wg03 | lazarus-worker-03 | 17.0.0.12 | 149.22.80.96 | Active |
| wg04 | lazarus-worker-04 | 17.0.0.13 | 149.102.228.68 | Active |
| wg05 | lazarus-worker-05 | 17.0.0.14 | 149.22.94.166 | Active |
| wg06 | lazarus-worker-06 | 17.0.0.15 | 89.187.170.182 | Active |
| wg07 | lazarus-worker-07 | 17.0.0.16 | 149.88.30.82 | Active |
| wg08 | lazarus-worker-08 | 17.0.0.17 | 149.22.94.58 | Active |
| wg09 | lazarus-worker-09 | 17.0.0.19 | 185.244.215.14 | Active |
| wg10 | lazarus-worker-10 | 17.0.0.20 | 149.22.84.8 | Active |
| wg11 | lazarus-worker-11 | 17.0.0.21 | 89.187.180.44 | Active |
| wg12 | lazarus-worker-12 | 17.0.0.22 | 149.88.105.226 | Active |
| wg13 | lazarus-worker-13 | 17.0.0.23 | 95.173.221.53 | Active |
| wg14 | lazarus-worker-14 | 17.0.0.24 | 185.98.170.12 | Active |
| wg15 | lazarus-worker-15 | 17.0.0.25 | 154.47.25.244 | Active |
References¶
- WireGuard Documentation
- OpenWRT WireGuard
- OpenWRT Policy Routing
- Scripts:
scripts/wireguard/