Skip to content

Proxmox LXC container can't write to a NAS share: fix permissions with real UID/GID numbers

Unprivileged LXC sees 'Permission denied' or files owned by nobody on a NAS share? Mount it on the Proxmox host, bind it in, and line up the shifted UID/GID numbers.

advancedUpdated 2026-11-02

You bind-mounted a NAS share into a Proxmox LXC container. Inside the container, ls -l shows everything owned by nobody / nogroup, and any write fails with Permission denied. Or the reverse: the app writes fine, but on the NAS the new files belong to a strange user like 101000.

Nothing is broken. It’s ID mapping, and once you see the real numbers the fix is quick. This guide uses container ID 101, the share nas.local:/volume1/media (or //nas.local/media for SMB), host mount point /mnt/nas, and a container user alice with UID/GID 1000.

Why this happens: the 100000 offset

Proxmox creates containers as unprivileged by default. They use Linux user namespaces, so every user and group ID inside the container is a different number on the host. With the standard mapping:

Inside the containerOn the Proxmox host
root (0)100000
alice (1000)101000
any ID n (0–65535)100000 + n

This is a security feature. If something escapes the container, it lands on the host as an unimportant high-numbered user, not real root.

The side effects:

  • A host file owned by a number outside the container’s range (for example 0 or 1000 on the host) has no matching user inside. The container shows it as 65534, which is nobody / nogroup. The Proxmox wiki describes exactly this behaviour for bind mounts.
  • When alice writes, the host and the NAS see 101000, not 1000.
  • The Proxmox pct docs warn that unprivileged containers with bind mounts “might run into permission problems caused by the user mapping and cannot use ACLs”.

So the job is simple: make the owner of the share, as the host sees it, match the number the container user really has on the host.

Quick diagnosis: get the real numbers

Inside the container (from the host, pct enter 101, or SSH in), check who the app runs as:

id alice                 # e.g. uid=1000(alice) gid=1000(alice)
ls -ln /mnt/nas          # numeric owners as the container sees them (65534 = unmapped)

On the Proxmox host, look at the same folder by number:

ls -ln /mnt/nas          # numeric owners as the host sees them
grep -E '^(mp[0-9]+|unprivileged|lxc.idmap)' /etc/pve/lxc/101.conf

Read it like this:

  • unprivileged: 1 and no lxc.idmap lines means the standard +100000 offset. Container UID 1000 = host UID 101000.
  • Host shows owner 0 or 1000, container shows 65534: the share is owned by a number the container can’t see. Fix below.
  • Host shows 101000 and the container shows 1000: ownership already matches. If writes still fail, look at file modes (dir_mode/file_mode for SMB) or the NAS’s own export rules.

Use the app’s real user. Many container apps run as a service user, not alice, so run id for that one. For Docker inside the LXC, it’s whatever PUID/PGID or user: the container uses.

Step 1: Mount the share on the Proxmox host

An unprivileged container generally can’t mount NFS or CIFS by itself, so the usual pattern is: the host mounts the share, then a bind mount passes the folder into the container. The pct docs recommend a folder reserved for this purpose (they suggest a tree under /mnt/bindmounts). Never bind-mount system folders like /, /var or /etc, and the source path must not contain symlinks.

On the host:

mkdir -p /mnt/nas

SMB/CIFS

With CIFS, the owner you see on the Proxmox side is mostly set by the client. Without Unix extensions, mount.cifs shows every file and folder as owned by the uid= and gid= you give it. If you leave them out, it defaults to 0 (root), and that’s why the container sees nobody. If the server negotiates Unix extensions, it can hand out its own owners instead. forceuid and forcegid tell the client to ignore those and show your values. These options only change what Linux displays and checks locally. They don’t change who owns the files on the NAS.

Put the SMB login in a file readable only by root, not in fstab:

cat > /root/.smb-nas <<'EOF'
username=alice
password=CHANGE-ME
EOF
chmod 600 /root/.smb-nas

Then add one line to /etc/fstab. This one is for strategy (a) below (shifted IDs):

//nas.local/media  /mnt/nas  cifs  credentials=/root/.smb-nas,uid=101000,gid=101000,forceuid,forcegid,file_mode=0770,dir_mode=0770,_netdev,x-systemd.automount,noatime  0  0

What matters:

  • uid=101000,gid=101000: the shifted IDs, so the container sees owner 1000.
  • file_mode=0770,dir_mode=0770: the owner and group get full access, others get nothing. These apply when the server doesn’t use Unix extensions (the usual case for a NAS).
  • _netdev marks it as a network mount. x-systemd.automount mounts it when first used, which helps if the NAS boots slower than Proxmox. The Proxmox forum tutorial uses both.

The SMB login (alice in the credentials file) decides who the NAS lets in and what that account may do. The stored owner of new files is up to the NAS: its share settings (some NASes force an owner or inherit one from the parent folder) and, if Unix extensions are in use, possibly the IDs the client sends. Don’t assume the NAS-side owner. Check it on the NAS during verification.

After any fstab edit, have systemd re-read it. With x-systemd.automount, start the automount unit while the share is not mounted, then touch the path to trigger the mount (the unit name comes from the path: /mnt/nas becomes mnt-nas):

systemctl daemon-reload
systemctl start mnt-nas.automount
ls /mnt/nas              # first access mounts the share

NFS

With NFS (the usual sec=sys, AUTH_SYS), it’s the other way round. The client sends numeric UIDs/GIDs and the server decides. A file alice creates in the container arrives at the NAS as UID 101000.

nas.local:/volume1/media  /mnt/nas  nfs  defaults,_netdev,x-systemd.automount  0  0

On the NAS side, what counts is the export options (in /etc/exports on a Linux NAS, or in the NAS’s NFS permissions screen):

  • root_squash is the default. Requests from UID/GID 0 become the anonymous user. So chown run as root on the Proxmox host fails or does nothing on the share. Do ownership changes on the NAS itself.
  • all_squash maps every user to the anonymous user. anonuid= / anongid= choose who that is (default 65534). With all_squash,anonuid=1000,anongid=1000, the NAS treats every request as coming from 1000, whatever number the client sends. New files are stored as 1000.

Squashing only changes who the request runs as. It doesn’t change the owners the NAS reports back. So with this export, new files show as 1000 on the Proxmox host and as 65534 (nobody) inside a container with the default mapping. Writes still work, because the NAS acts as 1000 either way, but ls -l in the container looks wrong. Squashing grants no extra rights either: the anonymous ID (1000 here) still needs permission on the existing folders and files on the NAS. If files must show as 1000 inside the container, use strategy (b).

A Linux export line using that approach looks like:

/volume1/media  192.168.1.10(rw,all_squash,anonuid=1000,anongid=1000)

Use your Proxmox host’s IP, not a whole subnet, if you can. Run exportfs -ra on the NAS after editing. On appliance NASes the same settings appear under names like “squash” or “map all users to”; the exact wording varies.

Reload systemd and mount it, as in the SMB section:

systemctl daemon-reload
systemctl start mnt-nas.automount
ls -ln /mnt/nas

Step 2: Bind-mount it into the container

First see which mount point slots are already in use:

pct config 101 | grep '^mp'

Gotcha: pct set 101 -mp0 ... replaces any existing mp0, which can quietly cut off another app’s storage. Pick a free number (mp1, mp2…), or update the existing entry if it’s already your NAS mount.

pct set 101 -mp0 /mnt/nas,mp=/mnt/nas

This adds mp0: /mnt/nas,mp=/mnt/nas to /etc/pve/lxc/101.conf. Add ,ro=1 for read-only. If the container can migrate between cluster nodes that all have the same mount, shared=1 marks it as available on all nodes. It doesn’t share anything itself.

Restart the container so it sees the mount: pct stop 101 && pct start 101.

Step 3: Pick a fix strategy

(a) Map ownership to the shifted ID (simplest, try this first)

Leave the container’s mapping alone and make the share belong to 101000 on the host.

  • CIFS: you already did it with uid=101000,gid=101000 in fstab. If you changed those options after the container started, apply them in the order below.
  • NFS: the files on the NAS must be owned by 101000 (change ownership on the NAS, since root squash blocks it from Proxmox). Or use the all_squash,anonuid= approach above: writes work, but files show as nobody inside the container.

Before you start: a running container keeps its hold on the old mount, so remounting on the host doesn’t reliably reach it. Whenever you change mount options, do it in this order. Stop every container that uses the share (pct stop 101, and any others). Unmount it (umount /mnt/nas). Edit fstab, then systemctl daemon-reload and systemctl start mnt-nas.automount. Check the owners with ls -ln /mnt/nas. Then start the containers again (pct start 101).

For several services with different users, the forum tutorial uses a shared group instead: mount with gid=110000, then inside the container groupadd -g 10000 lxc_shares (10000 + 100000 = 110000) and add each service user to that group with usermod -aG lxc_shares <user>.

(b) A custom idmap: container UID 1000 = host UID 1000

Choose this when the NAS already stores files as 1000 (typical for NFS shares that other Linux machines also use) and you want the same number everywhere. You punch a one-ID hole in the standard mapping, so container 1000 becomes host 1000, and everything else stays shifted.

Stop the container first (pct stop 101). Then add these lines to /etc/pve/lxc/101.conf:

lxc.idmap = u 0 100000 1000
lxc.idmap = g 0 100000 1000
lxc.idmap = u 1000 1000 1
lxc.idmap = g 1000 1000 1
lxc.idmap = u 1001 101001 64535
lxc.idmap = g 1001 101001 64535

Each line reads: type (u user, g group), first ID inside, first ID on the host, how many. Together, the three ranges of each type cover the container’s full 0–65535 with no gaps or overlaps:

  • 0–999 → 100000–100999 (shifted as before)
  • 1000 → 1000 (the hole)
  • 1001–65535 → 101001–165535 (shifted as before, 1001 + 64535 = 65536)

Then allow host root to hand out ID 1000. Add one line to each of /etc/subuid and /etc/subgid on the host, keeping the existing root:100000:65536 line:

root:1000:1

Now make the host side match:

  • CIFS: change the fstab line to uid=1000,gid=1000. The container is already stopped, so unmount, reload and remount as in the box above.
  • NFS: files on the NAS should be owned by 1000. With this mapping, all_squash,anonuid=1000,anongid=1000 also shows files as 1000 inside the container.
  • Local folders you bind-mount: chown -R 1000:1000 /path.

Start it again: pct start 101. If it refuses, lxc-start -F -n 101 runs it in the foreground and shows the error. A typo in a range or a missing /etc/subuid line are the usual causes.

Gotcha: changing the mapping on a container that already has data doesn’t rewrite any files. Anything alice created before is still owned by 101000 on the host and will look like nobody inside afterwards. Fix those files with chown to the new number.

Why not just go privileged, or chmod 777?

Risk: a privileged container has no ID shift: container root is host root. Proxmox’s docs say the LXC team considers privileged containers unsafe and recommends them only in trusted environments. One bug in an internet-facing app could reach the whole host. chmod -R 777 on the share lets every user and every service on every machine that mounts it change or delete your files, and it hides the real problem. With CIFS it often doesn’t even work, because the mount options, not the files, set the permissions. Either fix takes ten minutes; both shortcuts stay with you.

Verify it worked

Inside the container, as the app’s user:

su - alice -c 'touch /mnt/nas/lxc-write-test && ls -ln /mnt/nas/lxc-write-test'

There should be no error. The owners you should see depend on the setup:

SetupInside the containerOn the Proxmox host
Strategy (a): CIFS uid=101000, or NFS files owned by 1010001000 1000101000 101000
Strategy (b): custom idmap, host side owned by 10001000 10001000 1000
NFS all_squash,anonuid=1000 with the default mapping65534 65534 (nobody)1000 1000

Finally, check the file on the NAS itself (its file browser or a shell). Its owner should be the user you meant. For SMB, that depends on the NAS’s settings, not the numbers above. Then delete the test file.

Common gotchas

  • The host mount must be there before the container starts. If it isn’t, the container gets the empty /mnt/nas folder, and the app may happily write to the host’s local disk. x-systemd.automount helps. If the NAS is itself a VM or container on the same Proxmox host, give it a lower start order and an up delay so it starts first: qm set <nas-vmid> -startup order=1,up=60 for a VM, or pct set <nas-vmid> -startup order=1,up=60 for a container. The up delay is a fixed wait in seconds, not a check that NFS or SMB is actually ready, so allow enough time. Containers without a start order always start after those with one. Check that onboot: 1 is set only where you want it.
  • Backups: the pct docs state that the contents of bind mounts are not backed up by vzdump. The backup flag only applies to volume mount points. Back up the NAS separately.
  • NFS root_squash: chown as root from the Proxmox host fails with “Operation not permitted” or silently does nothing. Do it on the NAS.
  • CIFS shows the right owner but writes still fail: check file_mode/dir_mode, and that the SMB account itself has write access on the NAS.
  • Docker inside the LXC: find the UID/GID the app process really runs as. Some images (such as LinuxServer.io’s) set it with PUID/PGID environment variables. Others use Compose’s user: setting or a fixed user built into the image. Normal rootful Docker adds no extra ID shift. Only rootless Docker or userns-remap adds another mapping on top.
  • Editing config while running: stop the container before changing lxc.idmap lines, then start it again.

Sources