JaySync Lab
Networking

Tailscale Routing

Bare-metal VPN placement and the MagicDNS conflict fix.

Architectural Placement

Tailscale is NOT deployed inside an LXC or VM. It is installed directly on the bare-metal Proxmox hypervisor (Debian OS).

Engineering Rationale:

  • Management Access: Guarantees remote access to the Proxmox Web GUI (Port 8006) even if the internal virtual network fails.
  • Subnet Router Capability: Acts as a gateway to expose the internal 192.168.1.x subnet to authenticated remote devices.

The MagicDNS Conflict

By default, Tailscale's MagicDNS overrides the host's /etc/resolv.conf to use 100.100.100.100. On a bare-metal hypervisor without a dedicated DNS manager like systemd-resolved, this breaks outbound domain resolution for the host, causing critical operations (like fetching Proxmox community scripts) to fail with "Temporary failure in name resolution".

The Permanent Engineering Fix

To force Tailscale to relinquish DNS control while maintaining the VPN mesh network, the following commands were executed on the Proxmox Node Shell:

# 1. Instruct Tailscale to stop modifying host DNS
tailscale set --accept-dns=false
 
# 2. Hardcode reliable upstream DNS for the Proxmox host
echo -e "nameserver 1.1.1.1\nnameserver 8.8.8.8" > /etc/resolv.conf

Conclusion: This fix ensures the hypervisor can reliably reach the internet and download packages independently of the lab's internal DNS containers.

Full Subnet Routing (implemented 2026-07-15)

The gap that existed: a remote device connected to the tailnet could only reach the Proxmox host itself (its own Tailscale IP). It could not reach any other device on 192.168.1.0/24 — Pi-hole, Uptime Kuma, Home Assistant, or the router's own admin UI — because Tailscale was only acting as an access node for the host, not as a subnet router for the wider LAN.

The fix, applied on the existing bare-metal Tailscale install (no new container):

  1. Enabled IP forwarding on the Proxmox host (net.ipv4.ip_forward=1, net.ipv6.conf.all.forwarding=1 in /etc/sysctl.conf, applied with sysctl -p).
  2. Re-advertised the route, keeping the existing --accept-dns=false fix from above:
    tailscale up --advertise-routes=192.168.1.0/24 --accept-dns=false
  3. Approved the route in the Tailscale admin console (Machines → this host → Edit route settings → enabled 192.168.1.0/24). Advertised routes sit inactive until approved — this was indeed why remote access didn't work before.
  4. Confirmed "Accept routes" is enabled on the remote test device.

Verified end-to-end: tested from a real phone on mobile data (off the home WiFi/VLAN entirely), with Tailscale connected — the Proxmox GUI, Pi-hole, and the router's own admin page were all reachable by their normal 192.168.1.x addresses, exactly as if on-site.

Split-DNS for friendly hostnames (implemented 2026-07-16): Pi-hole (192.168.1.101) is set as a nameserver in the Tailscale admin console's DNS tab, restricted to the domain lab.jaysynclab.com rather than overriding all DNS tailnet-wide. This means only *.lab.jaysynclab.com lookups get forwarded to Pi-hole — everything else on a remote device keeps using its normal DNS untouched. Verified end-to-end from a real off-VLAN phone: pihole.lab.jaysynclab.com and kuma.lab.jaysynclab.com both resolve and load correctly. See the Reverse Proxy page for the full picture, including one service (Home Assistant) that's still off-VLAN-only pending further investigation.

Execution notes (deviations from the original plan)

This was executed remotely via SSH rather than by hand, using a new dedicated claude-agent account created for the purpose instead of root (least-privilege — its sudo access is scoped to only tailscale, sysctl, and appending to /etc/sysctl.conf; see /etc/sudoers.d/claude-agent on the host). Two things didn't match the original plan and had to be worked around:

  • sudo wasn't installed on the host at all — this Proxmox install had only ever been managed as root directly. Installed it (apt-get install sudo) before the scoped account could do anything.
  • /etc/sysctl.conf didn't exist yet — appending to it via tee -a created it fresh; no issue, just worth noting it wasn't a pre-existing file as the original plan assumed.
  • Binary paths for tailscale (/usr/bin/tailscale) and sysctl (/usr/sbin/sysctl) had to be confirmed on the host rather than assumed, since the sudoers rule has to match the exact resolved path.

On this page