Skip to content

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):

grep 'dev/net/tun' /etc/pve/lxc/101.conf
# Expected: dev0: /dev/net/tun,gid=0,uid=0

Bring Tailscale up:

pct exec 101 -- tailscale up --accept-routes=false --accept-dns=false

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.x address, not 192.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 admin on Traefik
  • tag:user - Traefik port 80/443 only; Traefik's ipAllowList middleware further restricts which services are visible
  • tag: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:

pct exec <vmid> -- tailscale update

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:

pct exec <vmid> -- tailscale up --accept-dns=false  # add other flags as needed

Troubleshooting

*.home.example.com not resolving on remote device

  1. Confirm Tailscale DNS is set to CT 101's Tailscale IP (not LAN IP) in the admin console
  2. Confirm "Override local DNS" is enabled
  3. Test DNS directly: dig home.example.com @<pihole-ts-ip> from the remote device
  4. Confirm Pi-hole listening mode is All Interfaces (Settings → DNS → Interface settings)

Subnet route not working

  1. Confirm the route is approved in Tailscale admin console → Machines → management → Edit route settings
  2. Confirm CT 100 is running and Tailscale is up: pct exec 100 -- tailscale status
  3. Check that your device has a route to 192.168.50.0/24 via 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 in your ACL config. The ipAllowList middleware in Traefik is a secondary layer - the ACL is the primary gate.