VPN & Tunneling - Street-Level Ops¶
Quick Diagnosis Commands¶
When the VPN is down and nobody can access internal resources:
# 1. Is the WireGuard interface up?
wg show
ip link show wg0
# 2. Is the tunnel passing traffic?
wg show wg0
# Check "latest handshake" — if > 2 minutes, something is wrong
# Check "transfer" — should show non-zero rx/tx
# 3. Can you ping across the tunnel?
ping -c 3 10.0.0.1 # ping the VPN server's tunnel IP
# 4. Check routing
ip route show
ip route get 10.0.0.1
# 5. Is the VPN port open?
ss -ulnp | grep 51820 # WireGuard
ss -ulnp | grep 1194 # OpenVPN
# 6. Check firewall
iptables -L -n | grep -i "51820\|wg0"
nft list ruleset 2>/dev/null | grep wg0
# 7. OpenVPN status
systemctl status openvpn
cat /var/log/openvpn-status.log
# 8. IPSec status
ipsec statusall
ipsec status
Pattern: WireGuard Debugging¶
# Full status with all peers
wg show wg0
# Example output:
# interface: wg0
# public key: abc123...
# private key: (hidden)
# listening port: 51820
#
# peer: def456...
# endpoint: 203.0.113.5:51820
# allowed ips: 10.0.0.2/32
# latest handshake: 32 seconds ago <-- GOOD (< 2 min)
# transfer: 1.24 MiB received, 3.45 MiB sent
# If "latest handshake" is missing or very old:
# 1. Key mismatch (most common)
# 2. Endpoint unreachable (firewall/NAT)
# 3. AllowedIPs mismatch between peers
# Debug connectivity
# From client:
ping -c 3 10.0.0.1 # ping server tunnel IP
traceroute 10.0.0.1
# Check if UDP port is reachable
nc -zu vpn.example.com 51820
# Check kernel module
lsmod | grep wireguard
modprobe wireguard
# Enable kernel debug logging (verbose, use briefly)
echo module wireguard +p > /sys/kernel/debug/dynamic_debug/control
dmesg -w | grep wireguard
# Disable when done:
echo module wireguard -p > /sys/kernel/debug/dynamic_debug/control
Gotcha: MTU Inside Tunnels¶
Tunnels add headers, reducing the effective MTU. If you do not account for this, you get mysterious connectivity issues — small packets work, large ones fail silently.
Standard Ethernet MTU: 1500 bytes
WireGuard overhead: 60 bytes (40 IPv6 or 20 IPv4 + 8 UDP + 32 WG)
OpenVPN overhead: ~70 bytes (varies by cipher)
IPSec overhead: ~60-80 bytes (varies by mode)
Effective MTU through WireGuard: 1500 - 60 = 1440
Effective MTU through OpenVPN: 1500 - 70 = 1430
# Check current MTU
ip link show wg0
# Set correct MTU (WireGuard)
ip link set mtu 1420 dev wg0
# Or in wg0.conf
[Interface]
MTU = 1420
# Test with large packets
ping -c 3 -s 1400 -M do 10.0.0.1 # -M do = don't fragment
# If this fails but ping -s 1200 works, MTU is the problem
# Find the correct MTU
ping -c 1 -s 1400 -M do 10.0.0.1 # try 1400
ping -c 1 -s 1350 -M do 10.0.0.1 # try 1350
# Binary search until you find the max that works
Gotcha: DNS Leaks¶
You set up a full-tunnel VPN but DNS queries still go to your ISP's resolver, leaking which domains you visit.
# Check which DNS server you are using
cat /etc/resolv.conf
resolvectl status
# Test for DNS leaks
dig +short whoami.akamai.net
# If this returns your ISP's IP, DNS is leaking
# WireGuard fix: set DNS in client config
[Interface]
DNS = 10.0.0.1 # use VPN server's DNS
# OpenVPN fix: push DNS to clients
push "dhcp-option DNS 10.8.0.1"
# systemd-resolved integration (modern Linux)
resolvectl dns wg0 10.0.0.1
resolvectl domain wg0 "~." # route ALL DNS through this interface
Pattern: OpenVPN Log Reading¶
# Real-time log monitoring
tail -f /var/log/openvpn.log
# Key log messages and what they mean:
# GOOD: Connection established
# "Initialization Sequence Completed"
# "Peer Connection Initiated with [AF_INET]203.0.113.5:1194"
# BAD: TLS handshake failure
# "TLS Error: TLS key negotiation failed"
# → Certificate mismatch, expired cert, or wrong CA
# BAD: Authentication failure
# "AUTH_FAILED"
# → Wrong credentials or certificate revoked
# BAD: Timeout
# "Connection reset, restarting"
# "Inactivity timeout"
# → Network issue, firewall blocking, or keepalive too aggressive
# Check connected clients (server side)
cat /var/log/openvpn-status.log
# Count connected clients
grep "^CLIENT" /var/log/openvpn-status.log | wc -l
Pattern: SSH Tunnel Quick Setups¶
# Scenario 1: Access a database behind a bastion
ssh -L 5432:db-server:5432 -N bastion.example.com &
psql -h localhost -p 5432 -U dbuser mydb
# Scenario 2: Access a web app behind a firewall
ssh -L 8080:internal-app:80 -N bastion.example.com &
curl http://localhost:8080
# Scenario 3: SOCKS proxy for browsing internal sites
ssh -D 1080 -N bastion.example.com &
curl --socks5 localhost:1080 http://internal-wiki.corp/
# Scenario 4: Expose local dev server to remote team
ssh -R 8080:localhost:3000 -N shared-server.example.com
# Team accesses shared-server:8080 to reach your localhost:3000
# Scenario 5: Jump through multiple bastions
ssh -J bastion1.example.com,bastion2.example.com internal-server
# Kill background SSH tunnels
ps aux | grep "ssh -[LDNR]" | grep -v grep
kill $(pgrep -f "ssh -L 5432")
Gotcha: Split-Brain Routing¶
You configure a full-tunnel VPN but forget to exempt the VPN server's own endpoint. The VPN traffic tries to route through the VPN, creating a routing loop. Connection drops immediately.
# Symptom: VPN connects briefly then drops
# Or: VPN connects but no traffic flows
# WireGuard: AllowedIPs = 0.0.0.0/0 captures ALL traffic,
# including the UDP packets to the VPN endpoint itself
# Fix: wg-quick handles this automatically when you set:
[Peer]
Endpoint = vpn.example.com:51820
AllowedIPs = 0.0.0.0/0
# wg-quick adds a specific route for the endpoint via the default gateway
# If using manual ip/wg commands, add the exception yourself:
ip route add 203.0.113.1/32 via 192.168.1.1 dev eth0 # endpoint via real gateway
wg set wg0 peer PUBKEY allowed-ips 0.0.0.0/0 # everything else via VPN
ip route add default dev wg0 # default route via VPN
Pattern: WireGuard Behind NAT¶
WireGuard uses UDP, which is NAT-friendly, but you need keepalive to maintain the NAT mapping:
# Client behind NAT: add PersistentKeepalive
[Peer]
PublicKey = SERVER_PUBLIC_KEY
Endpoint = vpn.example.com:51820
AllowedIPs = 10.0.0.0/24
PersistentKeepalive = 25 # send keepalive every 25 seconds
Without PersistentKeepalive, NAT mappings expire (typically 30-120 seconds), and the server can no longer reach the client. The client can still initiate connections, but the server cannot push traffic.
Gotcha: Keepalive Misconfiguration¶
# OpenVPN: keepalive 10 120
# → Send ping every 10 seconds
# → If no response in 120 seconds, restart connection
# Too aggressive: keepalive 1 5 (floods network, false restarts)
# Too passive: keepalive 60 600 (10 min to detect dead peer)
# WireGuard: PersistentKeepalive = 25
# → Only needed behind NAT
# → 25 seconds is the recommended value
# → Setting to 0 disables (fine if not behind NAT)
# → Setting too low wastes bandwidth on metered connections
Pattern: IPSec Troubleshooting¶
# Check IKE SA (Phase 1)
ipsec statusall | grep "IKE_SA"
# Check Child SA (Phase 2 — the actual tunnel)
ipsec statusall | grep "CHILD_SA"
# If Phase 1 fails:
# → Check pre-shared key or certificate
# → Check IKE version (ikev1 vs ikev2)
# → Check firewall (UDP 500, UDP 4500 for NAT-T)
# → Check time sync between peers
# If Phase 1 succeeds but Phase 2 fails:
# → Check traffic selectors (subnet mismatch)
# → Check ESP proposal (cipher mismatch)
# → Check firewall (ESP protocol 50)
# Restart IPSec
ipsec restart
# Debug logging
ipsec stroke loglevel ike 4
journalctl -u strongswan -f
# Verify traffic is being encrypted
tcpdump -i eth0 esp
# You should see ESP packets, not plaintext
Pattern: Quick WireGuard Peer Addition¶
# On the new client: generate keys
wg genkey | tee /etc/wireguard/privatekey | wg pubkey > /etc/wireguard/publickey
# On the server: add the peer (no restart needed)
wg set wg0 peer $(cat client-publickey) allowed-ips 10.0.0.5/32
# Save the running config to the file
wg-quick save wg0
# On the client: create config and bring up
cat > /etc/wireguard/wg0.conf << 'EOF'
[Interface]
PrivateKey = CLIENT_PRIVATE_KEY
Address = 10.0.0.5/24
DNS = 1.1.1.1
[Peer]
PublicKey = SERVER_PUBLIC_KEY
Endpoint = vpn.example.com:51820
AllowedIPs = 10.0.0.0/24
PersistentKeepalive = 25
EOF
wg-quick up wg0
Gotcha: Firewall Rules for VPN¶
Common ports and protocols that must be allowed:
WireGuard: UDP 51820 (or custom port)
OpenVPN: UDP 1194 (default) or TCP 443 (fallback)
IPSec: UDP 500 (IKE), UDP 4500 (NAT-T), Protocol 50 (ESP)
SSH Tunnel: TCP 22
# WireGuard firewall rules (iptables)
iptables -A INPUT -p udp --dport 51820 -j ACCEPT
iptables -A FORWARD -i wg0 -j ACCEPT
iptables -A FORWARD -o wg0 -j ACCEPT
iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
# Also enable IP forwarding
sysctl net.ipv4.ip_forward=1
echo "net.ipv4.ip_forward=1" >> /etc/sysctl.d/99-vpn.conf