Tailscale¶
Tailscale provides remote access to the homelab from anywhere. Two LXCs have Tailscale installed with distinct roles.
Architecture¶
Phone (Tailscale)
→ Pi-hole CT 101 (<pihole-ts-ip>) - DNS server
→ CT 100 subnet router (<mgmt-ts-ip>) - routes 192.168.50.0/24
→ any LAN IP (ACLs restrict to Traefik :80/:443 on .100)
- CT 101 is the global nameserver for the Tailscale network - all DNS queries from Tailscale clients go through Pi-hole
- CT 100 is the subnet router - advertises the full
192.168.50.0/24, ACLs restrict access to port 80/443 on 192.168.50.100 only
Tailscale Node IPs¶
| LXC | CT | Tailscale IP | Role |
|---|---|---|---|
| pihole | 101 | DNS server | |
| management | 100 | Subnet router (192.168.50.100/32) | |
| game-panel | 400 | Direct access | |
| minecraft-wings | 401 | Direct access |
TUN Device Setup (all LXCs)¶
Always use dev0 - do NOT use the cgroup2/mount.entry method (breaks on cgroupv2):
pct stop <vmid>
# Remove any bad entries if the helper script added them:
sed -i '/lxc.cgroup2.devices.allow: c 10:200/d' /etc/pve/lxc/<vmid>.conf
sed -i '/lxc.mount.entry: \/dev\/net\/tun/d' /etc/pve/lxc/<vmid>.conf
echo 'dev0: /dev/net/tun,gid=0,uid=0' >> /etc/pve/lxc/<vmid>.conf
pct start <vmid>
Install Method¶
Use the community-scripts helper - always enter the CT ID when prompted:
bash <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/tools/addon/add-tailscale-lxc.sh)
Tailscale Up Flags¶
| LXC | Command |
|---|---|
| CT 101 (DNS) | tailscale up --accept-routes=false --accept-dns=false |
| CT 100 (subnet router) | tailscale up --advertise-routes=192.168.50.0/24 --accept-dns=false --accept-routes=false |
| CT 400/401 (direct) | tailscale up --accept-routes=false --accept-dns=false |
Always use --accept-dns=false inside LXCs - otherwise Tailscale's DNS takes over and breaks local service connections.
Pi-hole DNS Config¶
Pi-hole must use listeningMode = "ALL" to accept queries from Tailscale clients (100.x.x.x range):
/etc/pihole/pihole.toml:
Tailscale admin DNS settings:
- Global nameserver: <pihole-ts-ip>
- Override local DNS: enabled
- MagicDNS: enabled
Tags¶
Tag-based permissions allow scoped access per device. Only you (the owner) can assign tags from the Tailscale admin Machines page. See management lxc for the full design and the per-service ipAllowList layer that works alongside this ACL.
| Tag | Who | Access |
|---|---|---|
tag:admin |
Your own devices | Full LAN subnet + all Tailscale nodes + Traefik admin |
tag:streaming |
Streaming devices (TV, phone, etc.) | Traefik 80/443, restricted by ipAllowList to Jellyfin + Seerr only |
tag:minecraft |
Player devices | Wings (ipAllowList to game-panel only |
| (untagged) | Any Tailscale node | Nothing - no grant applies |
There is no general "everyone" grant anymore - every non-admin tag's reach into Traefik is scoped down further by Traefik's own ipAllowList middleware per router (see management lxc), since Tailscale ACLs can't distinguish between services sharing the same port.
Access Control (ACLs)¶
Configured at tailscale.com/admin/acls.
{
"tagOwners": {
"tag:admin": ["autogroup:owner"],
"tag:streaming": ["autogroup:owner"],
"tag:minecraft": ["autogroup:owner"]
},
"grants": [
{"src": ["tag:admin"], "dst": ["100.64.0.0/10", "192.168.50.0/24"], "ip": ["*"]},
{"src": ["tag:minecraft"], "dst": ["<wings-ts-ip>"], "ip": ["*"]},
{"src": ["tag:admin", "tag:streaming", "tag:minecraft"], "dst": ["192.168.50.100"], "ip": ["tcp:80", "tcp:443"]},
{"src": ["*"], "dst": ["<pihole-ts-ip>"], "ip": ["udp:53", "tcp:53"]}
]
}
tag:admin: full access to all Tailscale IPs, full LAN subnet, and Traefik admintag:minecraft: direct access to Wings (CT 401, the actual game connection) + Traefik 80/443 (further narrowed to game-panel only by Traefik'sminecraft-sharedmiddleware)tag:streaming: Traefik 80/443 only (narrowed to Jellyfin + Seerr by Traefik'sstreaming-sharedmiddleware)- Everyone, including untagged: DNS to Pi-hole (
<pihole-ts-ip>:53) - required because "Override local DNS" is tailnet-wide, not per-tag; without this, any non-admin device's general internet DNS breaks the moment it connects (see management lxc) - Untagged: nothing beyond DNS - no grant to any LAN/Traefik destination
Key Takeaways¶
- Advertise
192.168.50.0/24- ACLs are the security layer, not the route scope - ACLs enforce port-level access - iptables inside the LXC does not work (Tailscale's
ts-inputchain intercepts first) listeningMode = "ALL"is required on Pi-hole - defaultLOCALrejects 100.x.x.x queries- Admin services only exist on Traefik
, and only tag:adminhas a grant reaching that port at all - no other tag should ever be added to it, since the port hosts every admin tool with no further per-service distinction possible at the ACL level - This ACL only controls remote (Tailscale) access - it has no effect on a device physically on the home network. LAN segmentation is a separate initiative.
- Adding a new service: decide tier first (admin-only →
adminentrypoint at 8443 +admin-onlymiddleware; shared with a specific tag →web/websecure+ a dedicatedipAllowListmiddleware); see management lxc