Remote Access - Tailscale¶
What you'll end up with¶
Full LAN access from any of your devices, anywhere, without opening a single router port. CT 100 advertises 192.168.50.0/24 as a Tailscale subnet route. CT 101 (Pi-hole) is the Tailscale DNS nameserver, so *.home.example.com resolves correctly on remote devices. Access is controlled per-device via Tailscale ACL tags.
Prerequisites¶
- CT 100 (management) running with TUN device passthrough configured
- CT 101 (Pi-hole) running with TUN device passthrough configured
- A Tailscale account
1. Install on CT 101 (Pi-hole - DNS role)¶
The Tailscale installer for LXC containers is a community helper script:
pct exec 101 -- bash <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/tools/addon/add-tailscale-lxc.sh)
Clean up bad TUN entries added by the script (these don't work correctly on cgroupv2):
pct stop 101
sed -i '/lxc.cgroup2.devices.allow: c 10:200/d' /etc/pve/lxc/101.conf
sed -i '/lxc.mount.entry: \/dev\/net\/tun/d' /etc/pve/lxc/101.conf
pct start 101
Confirm the correct dev0 entry is there (added by the CT script earlier):
Bring Tailscale up:
Follow the authentication URL that appears. After auth, note the Tailscale IP assigned to CT 101 in the Tailscale admin console - you'll need it for DNS config.
--accept-dns=false is required inside LXCs
Without this flag, Tailscale overwrites the LXC's /etc/resolv.conf with Tailscale DNS, which breaks local service resolution. Always use --accept-dns=false on LXC nodes.
2. Configure Pi-hole as Tailscale DNS¶
In the Tailscale admin DNS settings:
- Global nameserver: enter CT 101's Tailscale IP (the
100.x.x.xaddress, not192.168.50.101) - Override local DNS: enable
- MagicDNS: enable
This routes all DNS queries from Tailscale clients through Pi-hole, making *.home.example.com resolve correctly from anywhere on your tailnet.
3. Install on CT 100 (management - subnet router)¶
Same process:
pct exec 100 -- bash <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/tools/addon/add-tailscale-lxc.sh)
Clean up bad TUN entries:
pct stop 100
sed -i '/lxc.cgroup2.devices.allow: c 10:200/d' /etc/pve/lxc/100.conf
sed -i '/lxc.mount.entry: \/dev\/net\/tun/d' /etc/pve/lxc/100.conf
pct start 100
Bring up as subnet router:
pct exec 100 -- tailscale up \
--advertise-routes=192.168.50.0/24 \
--accept-dns=false \
--accept-routes=false
In the Tailscale admin console → Machines → management → Edit route settings → enable 192.168.50.0/24.
After this, any device on your tailnet can reach any 192.168.50.x IP directly.
4. Set up ACL tags¶
Tags control which devices get access to what. In Tailscale admin → ACLs, define tag ownership and grants.
Example ACL config:
{
"tagOwners": {
"tag:admin": ["autogroup:owner"],
"tag:user": ["autogroup:owner"],
"tag:guest": ["autogroup:owner"]
},
"grants": [
{
"src": ["tag:admin"],
"dst": ["100.64.0.0/10", "192.168.50.0/24"],
"ip": ["*"]
},
{
"src": ["tag:admin", "tag:user"],
"dst": ["192.168.50.100"],
"ip": ["tcp:80", "tcp:443"]
},
{
"src": ["tag:guest"],
"dst": ["<guest-service-ts-ip>"],
"ip": ["*"]
},
{
"src": ["*"],
"dst": ["<pihole-ts-ip>"],
"ip": ["udp:53", "tcp:53"]
}
]
}
What each grant does:
tag:admin- full access to all Tailscale IPs and the entire LAN subnet, including adminon Traefik tag:user- Traefik port 80/443 only; Traefik'sipAllowListmiddleware further restricts which services are visibletag:guest- direct access to a specific node only (e.g. a game server or shared service)- Everyone including untagged - DNS access to Pi-hole only; required because "Override local DNS" is tailnet-wide
Assign tags to devices in the Tailscale admin console → Machines.
Two-layer access control
Tailscale ACLs control which ports a device can reach at the network level. Traefik's ipAllowList middleware controls which services are visible at the application level. Both layers work together - ACLs alone can't distinguish between services sharing port 443.
5. Verify¶
From a remote device connected to your tailnet:
# DNS resolves via Pi-hole
dig home.example.com
# Expected: 192.168.50.100
# LAN is reachable via subnet route
ping 192.168.50.100
# Internal HTTPS works remotely
curl -sk https://glance.home.example.com | head -5
Day-2 operations¶
Updating Tailscale¶
Tailscale auto-updates by default on most platforms. To update manually on any CT:
Run this on each CT that has Tailscale installed (CT 100 and CT 101 in this setup, plus CT 400/401 if you set up Pterodactyl).
Adding a device to your tailnet¶
Install Tailscale on the device and sign in with your account. Assign it a tag in the Tailscale admin console.
Sharing access with someone else (e.g. Minecraft friends)¶
Use Tailscale's sharing feature: admin console → Machines → Share to invite someone to your tailnet with scoped access. Assign them tag:minecraft to limit their reach to game servers only.
Re-authenticating a node¶
Tailscale nodes use ephemeral auth by default; they re-authenticate automatically. If a node shows as expired:
Troubleshooting¶
*.home.example.com not resolving on remote device
- Confirm Tailscale DNS is set to CT 101's Tailscale IP (not LAN IP) in the admin console
- Confirm "Override local DNS" is enabled
- Test DNS directly:
dig home.example.com @<pihole-ts-ip>from the remote device - Confirm Pi-hole listening mode is All Interfaces (Settings → DNS → Interface settings)
Subnet route not working
- Confirm the route is approved in Tailscale admin console → Machines → management → Edit route settings
- Confirm CT 100 is running and Tailscale is up:
pct exec 100 -- tailscale status - Check that your device has a route to
192.168.50.0/24via the subnet router:ip route | grep 192.168.50(macOS:netstat -nr | grep 192.168.50)
Admin services reachable from non-admin Tailscale devices
Verify the tag:admin grant is the only one that includes ipAllowList middleware in Traefik is a secondary layer - the ACL is the primary gate.