BuildNot yet confirmed elsewhere1 publisher2 min readPublished
Tailscale in unprivileged LXC: the green dot that answers nothing
The standard --tun=userspace-networking advice produces a tailnet member you cannot SSH into. The repair is two lines of container config, not a privileged container.
The Engineer · Build desk
What happened
- The standard userspace-networking flag registers the container in the tailnet and reports healthy status, then SSH from a laptop hangs until TCP times out.
- The cause is that tailscaled runs gVisor's netstack inside its own process and never opens /dev/net/tun.
- Netstack's UDP has been partial for years, so mosh-server starts and prints its key while the client waits for datagrams that never arrive.
- The author's fix is two lines in the LXC config file, with the container remaining unprivileged.
Why it matters
- exposure Any process that misses the proxy variable sends traffic you believe is on the tailnet out over the ordinary default route, and nothing logs a failure.
- constraint Staying in userspace mode means your hardened sshd_config is no longer the thing answering, so tuned SSH policy has to be duplicated in a second place.
- decision The forum shortcut to working TUN asks you to hand a VPN-facing container a root that maps to host root, which is the choice this config removes.
- capability Each container can hold its own tailnet identity and ACL surface rather than depending on one subnet router to speak for all of them.
Rank the failure modes before you rank the fixes. A hung SSH session is loud, and you find it inside an hour [2]. The quiet one is outbound traffic: in userspace mode nothing on the system routes to 100.64.0.0/10, because there is no interface and no route, so every client has to be pointed at the SOCKS5 or HTTP proxy that tailscaled exposes on a local port [4]. Miss that variable in one systemd unit or one cron job and the packets take the normal default route with no error at all [5]. That is the sort of thing that stays wrong for months, and it is a property of the design rather than a mistake in it.
Tailscale SSH is what keeps the design's edges out of view. It genuinely works in userspace mode, which is why plenty of operators never discover the limitation, according to the post [7]. What they have is tailscaled acting as the SSH server, not OpenSSH answering on the container's own port, because tailnet packets terminate in netstack with no path to a kernel-owned socket unless you build one [6][7].
The debugging trail is where the time goes. Forum advice converges on three knobs, and by the post's account two of them are irrelevant to the problem: nesting governs cgroup hierarchy and procfs/sysfs views, and device access is a separate mechanism entirely [10], while AppArmor unconfined is named as the third dead end [11]. So of the three remedies in circulation, two change nothing about TUN and the third changes the container's security model [14]. The working answer sits in the mechanism none of them touch, and the post supplies it as two lines that leave the container unprivileged [1][10].
The test of success is also worth restating, because the admin console is not it. What you are buying is a kernel network interface that `ip addr` can see, a real address in the CGNAT range, and a direct WireGuard path rather than reachability borrowed from something else [13]. A green dot and a healthy `tailscale status` were available all along [2].
What to watch
- Whether Tailscale closes the netstack UDP gap, which would remove the strongest functional reason to leave userspace mode.
- Whether the device-access config keys survive Proxmox and LXC template changes, since a silently ignored line reverts you to the hanging-SSH state.
- Whether per-container identities multiply into an ACL set nobody maintains once the node count grows.
Clarity's read
What the record supports and how the coverage leans. The claims behind it follow.
Reality
- Evidence52
- Adoption
- Insufficient
- Hype gap+14
- Incentives30
- Confidence55
Claim ledger
Ranked by verification strength, evidence, and original report placement.
- [1]
Two lines in the LXC config file fix the problem, and the container stays unprivileged.
- [2]
With tailscale up --tun=userspace-networking, the node appears in the tailnet and tailscale status looks healthy, but SSH into that container from a laptop hangs until TCP gives up.
- [3]
In userspace mode tailscaled runs a userspace TCP/IP stack (gVisor's netstack) inside its own process and never opens /dev/net/tun.
- [4]
In userspace mode tailscaled exposes SOCKS5 and HTTP proxies on a local port; nothing on the system routes to 100.64.0.0/10 automatically because there is no interface and no route, so every client has to be told about the proxy (for example ALL_PROXY=socks5://localhost:1055/).
- [5]
Missing the proxy environment variable in a systemd unit, a cron job or a nested container makes traffic silently take the normal default route instead, with no error; the author calls this the worst failure mode a network can have.
- [6]
sshd binds 0.0.0.0:22 on the container's LAN interface, and packets arriving over the tailnet terminate inside tailscaled's netstack with no path to a socket the kernel owns unless built explicitly with tailscale serve or by switching to Tailscale SSH.
- [7]
Tailscale SSH works in userspace mode, which is why plenty of people never notice the limitation, but it is tailscaled's SSH implementation rather than OpenSSH; anyone relying on host key pinning, authorized_keys command restrictions, Match blocks or a tuned sshd_config then maintains two parallel SSH stories on the same box.
- [8]
Netstack's UDP support has been partial for years; mosh does not work, with mosh-server starting and printing its port and key while the client waits forever because the datagrams never arrive.
- [9]
Setting unprivileged: 0 makes TUN work but hands the container a root that maps to real host root, discarding the most valuable property of an unprivileged container; the author calls that trade exactly backwards for a machine on a VPN accepting inbound connections.
- [10]
--features nesting=1 does nothing for TUN: nesting controls whether the container can see and mount its own cgroup hierarchy and its own procfs/sysfs views, and device access is a completely separate mechanism.
- [11]
The author names AppArmor, and specifically the advice to set lxc.apparmor.profile: unconfined, as the third dead end.
- [12]
Instead of one node advertising routes on behalf of everyone else, each container running its own Tailscale carries its own identity, its own ACL surface and its own direct path to peers; the subnet router pattern is a working setup and this is an optional upgrade.
- [13]
The goal is containers that are real tailnet members with their own 100.64.0.0/10 address, reachable directly over WireGuard with a kernel network interface that ip addr can see, rather than reachable through something else.
- [14]
Of the three remedies the author says circulate on forums, two (nesting and AppArmor unconfined) do not affect TUN device access, and one (making the container privileged) works only by changing the container's security model.
Sources
1 independent publisher whose own reporting we read for this story.
- dev.toTailscale Kernel TUN in Unprivileged LXC: Direct SSH Without Userspace Networking
1 article · August 21, 2026
Topics and entities
Follow any of these and your For You feed starts watching them — no settings page required.