Tech Digest

Guide

Why does a Proxmox unprivileged LXC get permission denied on a bind mount?

An unprivileged container adds 100000 to every user and group id before the host sees it. The host folder belongs to someone the container cannot name, so it shows as nobody:nogroup and refuses the write. Either give the folder to the shifted id, or map that one id straight through.

Last reviewed

Why does an unprivileged Proxmox LXC container get permission denied on a bind mount?

Because Proxmox maps container ids 0 to 65535 onto host ids 100000 to 165535. A process running as uid 1000 in the container is uid 101000 on the host, and the host folder owned by 1000 is outside the map, so it shows as nobody:nogroup (65534) and the write is refused. If only this container uses the files, run chown -R 101000:101000 on the host folder. If the host or another machine needs them as 1000, map 1000 straight through: the idmap= mount point option on Proxmox VE 9.2 and later, or lxc.idmap lines plus /etc/subuid and /etc/subgid entries before that.

Because an unprivileged container does not use the host's user ids. Proxmox VE maps container ids 0 to 65535 onto host ids 100000 to 165535. A service running as uid 1000 in the container is uid 101000 on the host. Your host folder belongs to uid 1000, which falls outside that range. So the container shows it as nobody:nogroup, and the kernel treats the service as a stranger and refuses the write.

There are two fixes. Give the folder to the shifted id, chown -R 101000:101000, when only this container uses it. Map uid 1000 straight through when the host, another guest or a file server also needs to see it as 1000.

What "unprivileged" and "id mapping" mean#

Every file on Linux stores its owner as two numbers, a user id (uid) and a group id (gid). The kernel compares those numbers with the numbers of the process asking. Names such as alice or www-data are only labels looked up afterwards.

An unprivileged container is one whose ids are moved to a different range on the host. Proxmox's container documentation says root inside it "is mapped to an unprivileged user outside the container", and that this is the default for new containers. The move is done by the kernel's user namespaces. The list of moves is the id map, and LXC writes it as lxc.idmap lines.

Proxmox's own code sets the default. When a container is unprivileged and has no custom lines, pve-container writes exactly two:

text
lxc.idmap = u 0 100000 65536
lxc.idmap = g 0 100000 65536

The LXC manual reads each line as four values: u or g, the first id inside the container, the first id on the host, and how many ids follow. So container 0 is host 100000, container 1000 is host 101000, and container 65535 is host 165535. Any host id outside 100000 to 165535 has no container number at all. The kernel shows it as 65534, and ls prints that as nobody and nogroup.

A bind mount is a host folder made visible inside the container. Proxmox calls it a mount point, written as mp0: /mnt/bindmounts/shared,mp=/shared in /etc/pve/lxc/100.conf. It hands over the folder as it is: owners, modes and all. The documentation warns about this case: "With unprivileged containers you might run into permission problems caused by the user mapping and cannot use ACLs."

Two answers circulate, and they solve different problems#

The forum threads behind this error split into two camps. One says "chown the folder to 100000 plus the uid". The other says "use lxc.idmap". Both work. They are not alternatives for the same case.

The chown changes the files. After chown -R 101000:101000, the container sees the folder as 1000:1000 and writes freely. But on the host the folder now belongs to 101000. A Samba share on the host, your own login, or a second container with a different map now sees a stranger's folder.

The map changes the container. It tells the kernel to pass uid 1000 through unshifted. The files keep their real owner, and the host, the container and anything else agree on who 1000 is. The cost is a hand-written map you must keep right.

So the rule is this. If only this one container touches the files, chown. If anything else needs the real uid, map.

A worked example: a media folder shared with the host#

Take container 105 running a media server as uid 1000, gid 1000. The library sits on the host in /mnt/bindmounts/media, owned by the host user that fills it, also uid 1000.

On the host, ls -ln /mnt/bindmounts would show 1000 1000 media. Inside the container, uid 1000 is host 101000. Host 1000 is outside the range, so ls -ln /media would show 65534 65534. The folder's mode is 755, so the media server falls into "other", which can read but not write. It gets Permission denied.

Fix one, chown, works through like this:

bash
# on the Proxmox host
chown -R 101000:101000 /mnt/bindmounts/media

Now the container sees 1000:1000 and writes. But the host user who fills the library is locked out. Here that is the wrong trade, because the host user is the other writer.

Fix two maps uid 1000 and gid 1000 straight through. On Proxmox VE 9.2 or later, put it on the mount point itself:

bash
pct set 105 -mp0 '/mnt/bindmounts/media,mp=/media,idmap=u:1000:1000:1;g:1000:1000:1'

The quotes matter, because the shell would otherwise end the command at the semicolon. Both sides then see 1000:1000, and the rest of the container keeps the default map.

What we measured#

We did not run this on a Proxmox host. LXC builds its maps on the kernel's user namespaces, so we wrote the same maps into user namespaces on Linux 5.4 and ran ls -ln and touch from both sides. The NFS rows used an NFSv4.2 export from Linux nfsd, with default options (root squashing on), mounted on the host and seen from the namespace.

MapHost owner of the folderContainer ls -lnResult
Default 0 100000 655361000:100065534 65534uid 1000: Permission denied
Default1000:100065534 65534Container root: touch denied, chown Operation not permitted
Default101000:1010001000 1000uid 1000 writes; new file is 101000:101000 on host
Wiki map, 1005 passed through1005:10051005 1005uid 1005 writes; file is 1005:1005 on host
uid 1000 passed through, gid lines left at default1000:10001000 65534Writes, as owner; file is 1000:101000 on host
Same0:1000, mode 27751000 65534uid 1000 in group 1000: Permission denied
uid and gid 1000 passed through0:1000, mode 2775not listedWrites; file is 1000:1000
uid 1000 listed in two linesanynot startedMap refused: Invalid argument
Default, NFS share1000:1000 on server65534 65534Permission denied
Default, NFS share101000:101000 on server1000 1000Writes; server shows 101000:101000
1000 passed through, NFS share1000:1000 on server1000 1000Writes; server shows 1000:1000
None, host root on the NFS mount1000:1000 on servernot usedchown: Operation not permitted; mkdir: Permission denied

Two rows carry most of the confusion. The fifth is why "I mapped my uid and it still fails" threads exist: the uid lines were added and the gid lines were not. Files then come out with group 101000 on the host, and any access that depends on the group fails. The second row is why chown inside the container never helps. Container root is host 100000, which owns nothing outside the container's own range.

The idmap arithmetic, worked out#

Once a container config holds any lxc.idmap line, Proxmox stops adding its defaults. pve-container falls back to 0 100000 65536 only when it finds no lines at all. So you must write the whole map, for users and for groups.

The rule for passing one id, N, through:

text
lxc.idmap: u 0 100000 N
lxc.idmap: u N N 1
lxc.idmap: u N+1 100000+(N+1) 65535-N

Check the wiki's example with N = 1005. The lines are u 0 100000 1005, u 1005 1005 1, and u 1006 101006 64530. The counts add up to 65536, the container column has no gaps, and no id appears twice. Repeat all three as g lines.

Here is a case with more than one id: uid 1000 for the app, gid 1000 for its group, and a shared media group at gid 2000. The user side needs the three lines above with N = 1000. The group side needs five:

text
lxc.idmap: u 0 100000 1000
lxc.idmap: u 1000 1000 1
lxc.idmap: u 1001 101001 64535
lxc.idmap: g 0 100000 1000
lxc.idmap: g 1000 1000 1
lxc.idmap: g 1001 101001 999
lxc.idmap: g 2000 2000 1
lxc.idmap: g 2001 102001 63535

Each line starts where the last one ended: 1001 plus 999 is 2000, and 2001 plus 63535 is 65536. Each shifted line keeps host = container + 100000. Get one sum wrong and two lines claim the same id. The kernel then refuses the whole map, as our overlap row shows. Since Proxmox VE 8.0 the release notes say the container start also warns about conflicting manual lxc.idmap entries.

Then allow root to hand those ids out. The wiki adds one line per passed-through id to each file on the host:

text
# /etc/subuid
root:1000:1

# /etc/subgid
root:1000:1
root:2000:1

Colon or equals sign#

The wiki writes lxc.idmap = u 0 100000 1005, and its page was last changed on 16 March 2021. People ask whether that form still works in a Proxmox config, which uses key: value. It does. The current container documentation gives the same raw key both ways, lxc.init_cmd: /sbin/my_own_init and lxc.init_cmd = /sbin/my_own_init, and says such settings "are passed directly to the LXC low-level tools". What changed is the key's old spelling. The release notes for 8.0 say lxc.id_map, with an underscore, now counts as an error.

The 9.2 mount point option, and where it stops#

Proxmox VE 9.2, released on 21 May 2026, added an idmap option to mount points. The documentation says it overrides the container's map "for this mount point". Each entry is type:container:disk:range-size, separated by semicolons. u:123:456:1 shows disk uid 456 as uid 123 inside. passthrough maps every id unchanged. Ids you leave out "fall back to the container's lxc.idmap". The release notes add that it only works for unprivileged containers, and the source logs "ignoring 'idmap' option" for a privileged one.

This beats the six-line map when only one folder needs real ids. It changes that one mount and leaves every other id in the container shifted.

It does not work on a network share. pve-container applies the option with the kernel call mount_setattr and its MOUNT_ATTR_IDMAP flag. The kernel's fs/namespace.c refuses that with EINVAL when the filesystem type lacks FS_ALLOW_IDMAP. In the kernel source, ext4 carries the flag and nfs, nfs4, cifs and smb3 do not.

NFS or SMB mounted on the host, then bind-mounted in#

Here the id has one more hop. The container's uid 1000 becomes 101000 on the host, and the host sends 101000 to the file server. With the usual sec=sys security the server simply checks that number. The exports manual says nfsd bases its access control on "the uid and gid provided in each NFS RPC request".

That gives two fixes. Set the owner on the server itself to 101000. Or use container-wide lxc.idmap lines so the host sends 1000. Our NFS rows show both working.

Do not try the chown from the Proxmox host. Root squashing is the default, and the manual says it maps "requests from uid/gid 0 to the anonymous uid/gid". Our host root got Operation not permitted. Container root is not squashed either. It is 100000, not 0, so the server treats it as an ordinary user.

SMB works differently. The mount.cifs manual says the core protocol "does not provide unix ownership", so files show the owner set by uid= and gid=, and chown "will return success but have no effect". Mount the share on the host with uid=101000,gid=101000, and the container sees 1000:1000. When the server does send Unix owners, the manual points to forceuid and forcegid to override them. Whether a container should hold its database on such a share at all is another matter, covered in Why SQLite says database is locked on an NFS or SMB share.

Which fix to use#

Who else uses the folderFix
Nobody but this containerchown -R to 100000 + uid and gid on the host
Host or other guests, local disk, Proxmox VE 9.2+idmap= on the mount point
Same, before 9.2Full lxc.idmap map, plus /etc/subuid and /etc/subgid
NFS share mounted on the hostOwner set on the server, or container-wide lxc.idmap
SMB share mounted on the hostuid= and gid= mount options on the host

Making the container privileged is not on the list. Docker in LXC or a VM on Proxmox explains why unprivileged is the boundary that matters on a shared kernel.

When this answer stops applying#

This page covers Proxmox's default map and maps derived from it. A container with someone else's custom map needs its own arithmetic. Our measurements ran on the kernel mechanism, not on Proxmox: the pct commands, the 9.2 option and the start warning come from Proxmox's documentation and source, and we did not run them. SMB was not tested. Check two more things yourself. The documentation says "the contents of bind mount points are not backed up when using vzdump", and that you cannot snapshot them, so the data needs its own job, as Backups that actually restore sets out. And Docker inside the container adds a third layer of ids: a Docker service running as 1000 is container 1000 and host 101000, and the Docker side of that is in Why a container gets permission denied on a bind mount.

What to do next#

Run id inside the container for the service that fails, and ls -ln on the host folder. Add 100000 to the first number and compare it with the second. If only this container needs the folder, chown it to that number tonight. If anything else does, upgrade to 9.2 and use the mount option, or write out the full map with both u and g lines and test it with ls -ln from inside before you put data on it.

Questions#

Why do files in my LXC bind mount show as nobody nogroup?

Because the host owner of those files is outside the container's id map. Proxmox maps container ids 0 to 65535 onto host ids 100000 to 165535. A file owned by host uid 1000 has no number inside the container, so the kernel shows it as 65534, which Debian and Ubuntu name nobody and nogroup. Nothing is wrong with the files. Only the view from the container is different. Run ls -ln on the host to see the real numbers.

What uid should I chown a bind mount to for an unprivileged LXC?

100000 plus the uid the app uses inside the container. A service running as 1000 needs chown -R 101000:101000 on the host folder. One running as root in the container needs 100000. Check the number first with id inside the container, or ps -o uid,gid,cmd for a running service. Do the same arithmetic for the group. This only holds for the default map; a container with custom lxc.idmap lines needs the number its own map gives.

Does lxc.idmap with = work in a Proxmox container config?

Yes. The Proxmox container documentation shows raw LXC keys written both ways in /etc/pve/lxc/<id>.conf, as lxc.init_cmd: /sbin/my_own_init and as lxc.init_cmd = /sbin/my_own_init, and passes them to LXC unchanged. So the wiki's lxc.idmap = u 0 100000 1005 lines work as written. What no longer works is the old key name lxc.id_map, with an underscore: since Proxmox VE 8.0 its presence is an error.

Can I chown files inside an unprivileged container to fix a bind mount?

Not for files the container does not already own. Container root is host uid 100000, not host root. In our test, root inside a user namespace with the default map got Operation not permitted changing the owner of a folder owned by host uid 1000, and Permission denied creating a file in it. The fix has to be made on the Proxmox host, or on the file server if the folder is a network share.

Should I make the container privileged to fix bind mount permissions?

No. A privileged container shares ids with the host, so the error goes away, but container root becomes host root. Proxmox's documentation says root inside an unprivileged container is mapped to an unprivileged user outside it, and that mapping is what keeps a container escape from landing as root on your hypervisor. Fix the ownership or map one id instead. Both keep that protection for every other id.

Does the Proxmox 9.2 idmap mount option work on an NFS share?

Not as a per-mount mapping. The option is applied with the kernel's idmapped mounts, and in the Linux kernel source the NFS and SMB filesystem types do not carry the flag that allows them, so the kernel refuses with EINVAL. For a share mounted on the host, either set the owner on the file server to 100000 plus the uid, or use container-wide lxc.idmap lines, which change the ids the container sends rather than the mount.

Sources#

Published . Last reviewed . Found something out of date? Tell us and we will fix it and log the change.