LXD exposes a host character device to a container by adding a device node, not by teaching the guest to create one. If the container has no /dev/net/tun, the host may have the device and the guest may still be unable to open it. The unix-char entry is the manager-side half of that path, and it is deliberately narrower than a privileged container. That is why the host node and the container node deserve separate checks.

The mechanism is short enough to keep in view: LXD resolves a host path, creates a matching node inside the instance and adds the device to the container's device cgroup. The guest process then needs its own permission and capability to use the node for anything.

What the unix-char entry actually changes

A unix-char device tells LXD to expose one host character device at a chosen path inside the container. The entry has three parts that matter: the device label used by the manager, the source path on the host, and the path the guest sees. Only the last two describe the device itself. A typo in either one produces a node at a location the client never opens.

On the host, the device is normally /dev/net/tun. Inside the container you can keep the same path or choose another, but the path must match what the VPN client expects. Keeping /dev/net/tun avoids a second configuration problem, because most clients open that path without asking.

The manager applies the entry when the instance starts or when the device is hotplugged. It does not copy a driver into the guest and it does not create a tunnel interface. It makes the node visible and permits access through the container's device control group.

Declare the device on the instance, not inside the guest

Add the entry from the LXD administration context, naming the instance and a label you can recognize later. For a container called vpnbox, the command is:

lxc config device add vpnbox tun unix-char source=/dev/net/tun path=/dev/net/tun
lxc config device show vpnbox

The label tun is local to the instance configuration. It is not an interface name and it does not have to match anything in the guest. If a profile already provides the same device, adding a second entry creates a conflict rather than redundancy, so inspect the effective configuration first.

Editing files inside the container is the wrong layer. A mknod in a startup script may appear to work on a privileged container, then vanish on recreation or fail on a less privileged one. The manager owns the instance's device set; let it own the node too.

Match ownership and mode to the guest process

The unix-char entry can set uid, gid and mode for the node inside the container. Use them to match the account that runs the VPN client, rather than opening the device to every user or switching the container to privileged mode.

Visibility is not the same as permission. A node owned by root with mode 600 is visible to the service running as an unprivileged user and still unopenable by it. Check the guest account first, then set the ownership to that account, and keep the mode no wider than the client needs.

The process also needs the capability to configure a tunnel interface, typically CAP_NET_ADMIN in the container's network namespace. Passing the device does not grant that capability, and granting the capability does not supply the device; they are independent requirements.

Know what the entry does not grant

A visible node is not a working VPN. The client package still has to be installed, the profile still has to be valid, and the service still has to start. Authentication, routes and DNS remain the client's job, and a failure there is not evidence that the device entry is wrong.

systemd can add another restriction. A unit with DeviceAllow or a hardened device policy can deny access to a node that the container manager supplied. Check the unit's device policy when the node exists but the service reports a permission error, and change that policy rather than widening the container.

Hotplug is optional and explicit. An entry with required=false needs a source path and can appear or disappear as the host device changes. That lifecycle suits hardware that comes and goes; for a TUN device that the VPN needs at boot, an optional entry can leave the service starting before its prerequisite exists.

Verify visibility, open, and interface in that order

First confirm the node inside the intended container. The path, major and minor numbers and ownership should match the host device and the entry you added. A missing node means the manager did not apply the entry, or applied it to a different instance.

lxc exec vpnbox -- ls -l /dev/net/tun
lxc exec vpnbox -- sh -c 'test -c /dev/net/tun && echo char-device-ok'

Next confirm that the service account can open it. A short test as that account separates an ownership problem from a service problem. Finally, start the client and confirm that a tunnel interface appears; only that third step proves the whole path, because the first two can pass while the client profile or capability is still wrong.

Record the instance, device label, host source, guest path and owning account when the change is approved. Those five values are what a later recreation, migration or profile change has to preserve, and they are much easier to compare than a permission error with no context.

ဆက်စပ်ဆောင်းပါးများ