Skip to content

WireGuard Denver Tunnel

Overview

WireGuard VPN tunnel through rocky (Meanservers, Denver) to appear as if located in Denver, Colorado.

Component Value
Server rocky (193.8.172.100)
Exit Location Greenwood Village, CO
Tunnel Network 10.99.0.0/24
Port 51820/UDP
Protocol WireGuard

Architecture

┌─────────┐         WireGuard          ┌─────────┐
│  luna   │◄─────────────────────────►│  rocky  │──► Internet
│10.99.0.2│        10.99.0.0/24       │10.99.0.1│    (Denver IP)
└─────────┘                            └─────────┘
     │                                      │
     │ Starlink                             │ Meanservers
     │ (Colorado)                           │ (Denver)
     └──────────────────────────────────────┘

Clients

Client Tunnel IP Config Location Status
luna 10.99.0.2 ~/.wireguard/rocky-denver.conf ✅ Active
owl (VLAN) TBD OPNsense WireGuard 🔜 Planned

Usage (luna)

Shell functions are defined in ~/.zshrc:

# Connect to Denver
denver-up

# Disconnect from Denver
denver-down

# Check status
denver-status

Manual Commands

# Bring tunnel up (sudo env injects homebrew bash 5+ into PATH for wg-quick's shebang)
sudo env PATH="/opt/homebrew/bin:$PATH" wg-quick up ~/.wireguard/rocky-denver.conf

# Bring tunnel down
sudo env PATH="/opt/homebrew/bin:$PATH" wg-quick down ~/.wireguard/rocky-denver.conf

# Check WireGuard status
sudo wg show

# Verify Denver IP
curl https://ipinfo.io

Server Configuration (rocky)

Config file: /etc/wireguard/wg0.conf

[Interface]
Address = 10.99.0.1/24
ListenPort = 51820
PrivateKey = <from 1Password: wireguard_rocky_denver_server, vault: scandora-automation>

PostUp = iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
PostUp = iptables -A FORWARD -i wg0 -j ACCEPT
PostUp = iptables -A FORWARD -o wg0 -j ACCEPT
PostDown = iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE
PostDown = iptables -D FORWARD -i wg0 -j ACCEPT
PostDown = iptables -D FORWARD -o wg0 -j ACCEPT

[Peer]
# luna
PublicKey = z2IPPElnNhvnBXH0GzYZrtPAUMWv+78xVtidfxYIEXs=
AllowedIPs = 10.99.0.2/32

System configuration:

  • IP forwarding: net.ipv4.ip_forward=1 in /etc/sysctl.d/99-wireguard.conf
  • Firewall: ufw allow 51820/udp
  • Service: systemctl enable wg-quick@wg0

Client Configuration (luna)

Config file: ~/.wireguard/rocky-denver.conf

[Interface]
Address = 10.99.0.2/24
PrivateKey = <from 1Password: wireguard_luna_denver_client, vault: scandora-automation>
# macOS wg-quick bug 1: route -n get falsely skips /3 and /4 AllowedIPs entries.
PostUp = /sbin/route -q -n add -inet 16.0.0.0/4 -interface %i
PostUp = /sbin/route -q -n add -inet 32.0.0.0/3 -interface %i
PostUp = /sbin/route -q -n add -inet 64.0.0.0/3 -interface %i
PostUp = /sbin/route -q -n add -inet 112.0.0.0/4 -interface %i
PostUp = /sbin/route -q -n add -inet 128.0.0.0/3 -interface %i
PostUp = /sbin/route -q -n add -inet 176.0.0.0/4 -interface %i
PostUp = /sbin/route -q -n add -inet 208.0.0.0/4 -interface %i
PostUp = /sbin/route -q -n add -inet 224.0.0.0/3 -interface %i
# macOS wg-quick bug 2: split AllowedIPs mode skips endpoint exemption route,
# causing a routing loop. Add host route for endpoint via current default gateway.
# NOTE: /usr/sbin/netstat (full path) is required — sudo strips PATH so bare
# 'netstat' silently fails, leaving no exemption route and causing a routing loop.
PostUp = /sbin/route -q -n add -host 193.8.172.100 $(/usr/sbin/netstat -rn -f inet | awk '/^default/{print $2; exit}')
PostDown = /sbin/route -q -n delete -host 193.8.172.100

[Peer]
PublicKey = Y2G2hNJNj0XckPdVuxDMRYoEZpV97wAMcnGRAPE710w=
Endpoint = 193.8.172.100:51820
# Routes all public internet through rocky; excludes RFC1918 (10/8, 172.16/12,
# 192.168/16) and RFC6598 CGNAT (100.64/10, Starlink) so all private networks
# remain reachable at any site. wg-quick's endpoint exemption handled via PostUp.
AllowedIPs = 0.0.0.0/5, 8.0.0.0/7, 11.0.0.0/8, 12.0.0.0/6, 16.0.0.0/4, 32.0.0.0/3, 64.0.0.0/3, 96.0.0.0/6, 100.0.0.0/10, 100.128.0.0/9, 101.0.0.0/8, 102.0.0.0/7, 104.0.0.0/5, 112.0.0.0/4, 128.0.0.0/3, 160.0.0.0/5, 168.0.0.0/6, 172.0.0.0/12, 172.32.0.0/11, 172.64.0.0/10, 172.128.0.0/9, 173.0.0.0/8, 174.0.0.0/7, 176.0.0.0/4, 192.0.0.0/9, 192.128.0.0/11, 192.160.0.0/13, 192.169.0.0/16, 192.170.0.0/15, 192.172.0.0/14, 192.176.0.0/12, 192.192.0.0/10, 193.0.0.0/8, 194.0.0.0/7, 196.0.0.0/6, 200.0.0.0/5, 208.0.0.0/4, 224.0.0.0/3
PersistentKeepalive = 25

Notes:

  • All public internet routes through rocky; RFC1918 and CGNAT (Starlink 100.64/10) stay local
  • Works at any site (Owl, Blue, remote) — no hardcoded gateway in config
  • Two macOS wg-quick bugs require PostUp workarounds (see comments in config above)
  • To regenerate the AllowedIPs list: python3 scripts/testing/wireguard-allowedips-calc.py

Key Management

Keys are stored in 1Password:

Item Vault Contains
wireguard_rocky_denver_server scandora-automation Server public/private keys
wireguard_luna_denver_client scandora-automation Client public/private keys

Adding New Clients

  1. Generate keypair on client:
wg genkey | tee private.key | wg pubkey > public.key
  1. Add peer to rocky's /etc/wireguard/wg0.conf:
[Peer]
# client-name
PublicKey = <client-public-key>
AllowedIPs = 10.99.0.X/32
  1. Reload rocky's config:
sudo wg syncconf wg0 <(wg-quick strip wg0)
  1. Create client config with:
  2. Tunnel IP: 10.99.0.X/24
  3. Server public key: Y2G2hNJNj0XckPdVuxDMRYoEZpV97wAMcnGRAPE710w=
  4. Endpoint: 193.8.172.100:51820

IP Allocation

IP Client Notes
10.99.0.1 rocky Server
10.99.0.2 luna Workstation
10.99.0.3 (reserved) owl VLAN gateway
10.99.0.10-99 (reserved) owl VLAN clients

Troubleshooting

Tunnel up but no connectivity

  1. Check if handshake completed:
sudo wg show

Look for "latest handshake" - if missing, firewall may be blocking.

  1. Check IP forwarding on server:
ssh rocky 'cat /proc/sys/net/ipv4/ip_forward'

Should return 1.

  1. Check NAT rules:
ssh rocky 'sudo iptables -t nat -L POSTROUTING -v'

macOS wg-quick bash version error

macOS ships with bash 3.2, but wg-quick requires bash 4+. Use sudo env to inject Homebrew's bash 5 into PATH — wg-quick's #!/usr/bin/env bash shebang resolves it:

sudo env PATH="/opt/homebrew/bin:$PATH" wg-quick up ~/.wireguard/rocky-denver.conf

Why not sudo /opt/homebrew/bin/bash $(which wg-quick)? PostUp commands inherit the environment from wg-quick, which still runs with sudo's stripped PATH. This causes bare commands like netstat in PostUp to silently fail. Using sudo env PATH=... ensures all subprocesses (including PostUp) see the full PATH.

Future: OPNsense VLAN (owl-den01)

When in Iowa, configure:

  1. WireGuard interface on OPNsense
  2. VLAN owl-den01 for Denver exit traffic
  3. Policy routing to send VLAN traffic through WireGuard
  4. Any device on the VLAN appears in Denver

See: OPNsense WireGuard Documentation