8.7 KiB
Tailscale VPN Configuration
Overview
Tailscale provides secure remote access to the homelab via a mesh VPN. This document covers the configuration, subnet routing, and critical gotchas learned from troubleshooting.
Network Architecture
Remote Clients (MacBook, Phone)
│
▼ Tailscale Mesh (100.x.x.x)
│
┌───────┴────────┐
│ │
▼ ▼
PVE (Subnet Router) UCG-Fiber (Gateway)
100.113.177.80 100.94.246.32
│ │
│ 10.10.10.0/24 │
└──────────┬───────────┘
│
┌──────┴──────┐
│ │
PiHole TrueNAS
10.10.10.10 10.10.10.200
Device Configuration
| Device | Tailscale IP | Role | Accept Routes | Advertise Routes |
|---|---|---|---|---|
| PVE | 100.113.177.80 | Subnet Router (Primary) | NO | 10.10.10.0/24, 10.10.20.0/24 |
| UCG-Fiber | 100.94.246.32 | Gateway (backup) | NO | (disabled) |
| PiHole | 100.112.59.128 | DNS Server | NO | None |
| TrueNAS | 100.100.94.71 | NAS | Yes | None |
| Mac-Mini | 100.108.89.58 | Desktop | Yes | None |
| MacBook | 100.88.161.1 | Laptop | Yes | None |
| Phone | 100.106.175.37 | Mobile | Yes | None |
Critical Configuration Rules
1. Devices on the Advertised Subnet MUST Have --accept-routes=false
Problem: If a device is directly connected to 10.10.10.0/24 AND has --accept-routes=true, Tailscale will route local subnet traffic through the mesh instead of the local interface.
Symptom: Device can't reach neighbors on the same subnet; ip route get 10.10.10.X shows dev tailscale0 instead of the local interface.
Fix:
# On any device directly connected to 10.10.10.0/24
tailscale set --accept-routes=false
Affected devices:
- UCG-Fiber (gateway) - directly on 10.10.10.0/24
- PiHole - directly on 10.10.10.0/24
- PVE - directly on 10.10.10.0/24 (but is the subnet router, so different)
2. Only ONE Device Should Be Primary Subnet Router
Problem: Multiple devices advertising the same subnet can cause routing conflicts or failover issues.
Current Setup:
- PVE is the primary subnet router for both 10.10.10.0/24 and 10.10.20.0/24
- UCG-Fiber has subnet advertisement DISABLED (was causing relay-only connections)
To change subnet router:
- Go to https://login.tailscale.com/admin/machines
- Disable route on old device, enable on new device
- Or set primary if both advertise
3. VPNs on Tailscale Devices Can Break Connectivity
Problem: A full-tunnel VPN (like ProtonVPN with AllowedIPs = 0.0.0.0/0) will route Tailscale's DERP/STUN traffic through the VPN, breaking NAT traversal.
Symptom: Device shows relay-only connections with asymmetric traffic (high TX, near-zero RX).
Fix: Use split-tunnel configuration that excludes Tailscale traffic. See PiHole ProtonVPN Configuration below.
DNS Configuration
Tailscale Admin DNS Settings
- Nameserver: 10.10.10.10 (PiHole via subnet route)
- Fallback: None configured
How DNS Works
- Remote client enables "Use Tailscale DNS"
- DNS queries go to 10.10.10.10
- Traffic routes through PVE (subnet router) to PiHole
- PiHole resolves via Unbound (recursive) through ProtonVPN
Subnet Routing
Current Primary Routes
PVE advertises:
- 10.10.10.0/24 (LAN)
- 10.10.20.0/24 (Storage network)
Verifying Routes
# From MacBook - check who's advertising routes
tailscale status --json | python3 -c "
import sys, json
data = json.load(sys.stdin)
for peer in data.get('Peer', {}).values():
routes = peer.get('PrimaryRoutes', [])
if routes:
print(f\"{peer.get('HostName')}: {routes}\")"
Testing Subnet Connectivity
# Test from remote client
ping 10.10.10.10 # PiHole
ping 10.10.10.120 # PVE
ping 10.10.10.1 # Gateway
dig @10.10.10.10 google.com # DNS
PiHole ProtonVPN Split-Tunnel
PiHole runs a WireGuard tunnel to ProtonVPN for encrypted upstream DNS queries. The configuration uses policy-based routing to ONLY route Unbound's DNS traffic through the VPN.
Configuration File: /etc/wireguard/piehole.conf
[Interface]
PrivateKey = <key>
Address = 10.2.0.2/32
# CRITICAL: Disable automatic routing - we handle it manually
Table = off
# Policy routing: only route Unbound DNS through VPN
PostUp = ip route add default dev %i table 51820
PostUp = ip rule add fwmark 0x51820 table 51820 priority 100
PostUp = iptables -t mangle -N UNBOUND_VPN 2>/dev/null || true
PostUp = iptables -t mangle -F UNBOUND_VPN
PostUp = iptables -t mangle -A UNBOUND_VPN -d 10.0.0.0/8 -j RETURN
PostUp = iptables -t mangle -A UNBOUND_VPN -d 127.0.0.0/8 -j RETURN
PostUp = iptables -t mangle -A UNBOUND_VPN -d 100.64.0.0/10 -j RETURN
PostUp = iptables -t mangle -A UNBOUND_VPN -d 192.168.0.0/16 -j RETURN
PostUp = iptables -t mangle -A UNBOUND_VPN -d 172.16.0.0/12 -j RETURN
PostUp = iptables -t mangle -A UNBOUND_VPN -j MARK --set-mark 0x51820
PostUp = iptables -t mangle -A OUTPUT -p udp --dport 53 -m owner --uid-owner unbound -j UNBOUND_VPN
PostUp = iptables -t mangle -A OUTPUT -p tcp --dport 53 -m owner --uid-owner unbound -j UNBOUND_VPN
PostUp = iptables -t nat -A POSTROUTING -o %i -j MASQUERADE
PostDown = iptables -t mangle -D OUTPUT -p udp --dport 53 -m owner --uid-owner unbound -j UNBOUND_VPN
PostDown = iptables -t mangle -D OUTPUT -p tcp --dport 53 -m owner --uid-owner unbound -j UNBOUND_VPN
PostDown = iptables -t mangle -F UNBOUND_VPN
PostDown = iptables -t mangle -X UNBOUND_VPN
PostDown = ip rule del fwmark 0x51820 table 51820 priority 100
PostDown = ip route del default dev %i table 51820
PostDown = iptables -t nat -D POSTROUTING -o %i -j MASQUERADE
[Peer]
PublicKey = <ProtonVPN-key>
AllowedIPs = 0.0.0.0/0, ::/0
Endpoint = 149.102.242.1:51820
PersistentKeepalive = 25
Key Points:
Table = offprevents wg-quick from adding default routes- Only traffic from the
unbounduser to port 53 gets marked and routed through VPN - Local, private, and Tailscale (100.64.0.0/10) traffic is excluded
Troubleshooting
Symptom: Can't reach subnet (10.10.10.x) from remote
Check 1: Is PVE online and advertising routes?
tailscale status | grep pve
# Should show "active" not "offline"
Check 2: Is PVE the primary subnet router?
tailscale status --json | python3 -c "..." # See above
Check 3: Can PVE reach the target on local network?
ssh pve 'ping -c 1 10.10.10.10'
Symptom: Device shows "relay" with asymmetric traffic (high TX, low RX)
Cause: Usually a VPN or firewall blocking Tailscale's UDP traffic.
Check: Run netcheck on the affected device:
tailscale netcheck
Look for:
- Wrong external IP (indicates VPN routing issue)
- Missing DERP latencies
MappingVariesByDestIP: truewith no direct connections
Symptom: Local devices can't reach each other
Cause: --accept-routes=true on a device that's directly on the subnet.
Fix:
# Check current setting
tailscale debug prefs | grep -i route
# Disable accept-routes
tailscale set --accept-routes=false
Symptom: Gateway can ping Tailscale IPs but not local IPs
Check routing:
ip route get 10.10.10.120
# If it shows "dev tailscale0" instead of "dev br0", that's the problem
Fix: tailscale set --accept-routes=false on the gateway
Maintenance Commands
Restart Tailscale
# On Linux
systemctl restart tailscaled
# Check status
tailscale status
Re-advertise Routes (PVE)
tailscale set --advertise-routes=10.10.10.0/24,10.10.20.0/24
Check Connection Type
# Shows direct vs relay for each peer
tailscale status
# Detailed ping with path info
tailscale ping <tailscale-ip>
Force Re-connection
tailscale down && tailscale up
Known Issues
UCG-Fiber Relay-Only Connections
The UniFi gateway sometimes fails to establish direct Tailscale connections, falling back to relay. This appears related to memory pressure or the gateway's NAT implementation. Current workaround: use PVE as the subnet router instead.
Gateway Memory Pressure
The UCG-Fiber has limited RAM (~3GB) and can become unstable under load. The internet-watchdog service will auto-reboot if connectivity is lost. See GATEWAY.md.
Change History
2026-01-05
- Switched subnet router from UCG-Fiber to PVE
- Fixed PiHole ProtonVPN from full-tunnel to split-tunnel (DNS-only)
- Disabled
--accept-routeson UCG-Fiber and PiHole - Documented critical configuration rules
Last Updated: 2026-01-05