Introduction

Every time you create an LXC container in Proxmox VE, a checkbox in the wizard quietly decides how much trust that container is given over the host it runs on. It's labeled Unprivileged container, it's checked by default, and most people click through it without a second thought. That's usually the right call — but "usually" is doing a lot of work in that sentence, and there are specific situations where unchecking it is exactly what you need to do, and others where unchecking it is exactly what gets a homelab server compromised.

Unlike a virtual machine, an LXC container doesn't get its own kernel. It shares the Proxmox host's kernel directly, which is what makes containers so fast to boot and so light on RAM — but it's also what makes the privileged/unprivileged distinction matter so much more than it would for a VM. A VM is isolated by hardware-level virtualization no matter what you do inside it. A container's isolation is a software construct built on Linux namespaces, and how strong that construct is depends entirely on which of these two modes you picked when you created it.

This article is a deep dive into what privileged and unprivileged containers actually are under the hood, why the LXC project itself refuses to treat privileged-container escapes as CVE-worthy bugs, and how to make an informed decision — including how to convert an existing container from one mode to the other without losing its data.

What You Will Learn

  • What "privileged" and "unprivileged" actually mean at the kernel level in an LXC container
  • How UID/GID remapping works, and why it's the entire basis of unprivileged container security
  • Why the LXC upstream project treats the two modes so differently from a security standpoint
  • How to create a container in either mode, and how to check which mode an existing container is running in
  • How to convert a privileged container to unprivileged (and back) via backup and restore
  • The practical limitations unprivileged mode imposes — device access, NFS mounts, certain quota types, and nesting
  • A decision framework for picking the right mode per workload, plus common pitfalls and how to fix them

What Is This Feature?

Both privileged and unprivileged containers are still just LXC containers — same kernel, same underlying pct tooling, same namespaces and cgroups providing process, network, and filesystem isolation. The difference comes down to one specific kernel feature: the user namespace, and specifically whether UID/GID remapping is turned on inside it.

In a privileged container, there is no UID remapping. UID 0 (root) inside the container is UID 0 on the Proxmox host. If a process inside that container somehow breaks out of its namespace — through a kernel bug, a dangerous mount option, or a misconfigured bind mount — it steps out as root on your hypervisor. Full stop. Every VM, every other container, and the Proxmox host's own management stack is sitting right there, unprotected by anything except the container boundary that just failed.

In an unprivileged container, the kernel's user namespace support remaps every UID and GID inside the container to a different, unprivileged range on the host. Root (UID 0) inside the container typically becomes something like UID 100000 on the host — a completely ordinary, non-root account with no special permissions. If the same kind of escape happens in an unprivileged container, the process that breaks out isn't root at all. It's some anonymous, powerless UID that Proxmox and the rest of the host generally don't recognize as anything special. The kernel bug that let it escape is still a real problem, but the blast radius is dramatically smaller.

This is why the LXC upstream project — the people who actually maintain the container runtime Proxmox builds on — is blunt about it: unprivileged containers are considered "safe by design," while privileged containers are explicitly labeled unsafe. It's stated plainly in their own security documentation that container-escape bugs in privileged containers won't be treated as CVE-worthy security issues, because the entire privileged-container model assumes you already trust whatever's running inside it as much as you trust the host itself.

Since Proxmox VE 4.4, unprivileged has been the default in both the GUI wizard and the API, and that default has not changed since. The privileged option still exists — and still has legitimate uses — but you now have to deliberately opt into it.

Why It Matters

The reason this distinction gets so much attention isn't theoretical. Containers, by design, run their workload on the exact same kernel as the host managing your entire cluster. A VM escape is a rare, headline-grade event because it has to break through hardware virtualization boundaries. A container escape only has to break through software namespace boundaries running on a kernel the attacker's process is already executing on. That's a meaningfully easier bar to clear, which is exactly why the remapping unprivileged containers perform is worth the friction it sometimes introduces.

That friction is real, though, and it's the other half of this story. Unprivileged mode isn't free — a handful of things that "just work" in a privileged container require extra configuration, or don't work at all, once every UID inside the container is a stranger to the host:

  • Bind-mounted host directories show up owned by an unmapped, unrecognizable UID unless you explicitly configure an ID map entry for it. A directory that shows root:root ownership on the host can appear owned by nobody:nogroup inside an unprivileged container unless you do the extra mapping work.
  • Certain device passthrough scenarios — particularly ones needing raw device node access with specific permissions, like some GPU or storage passthrough setups — are considerably more fragile in unprivileged mode and sometimes require privileged mode as the practical path forward.
  • User quotas on certain storage backends only function correctly in specific combinations of container mode and storage type.
  • Nesting (running containers inside containers, or certain container-based tools that expect direct namespace access) needs the nesting feature explicitly enabled, and even then behaves differently than it would in a privileged container.

None of these are reasons to reach for privileged mode by default — they're reasons to understand exactly why you're reaching for it on the rare occasion you actually need to.

Prerequisites

  • A working Proxmox VE 8.x or 9.x host with at least one LXC container already created (or the ability to create one)
  • Root or sufficient administrative access to the Proxmox shell (SSH or the built-in web console)
  • A storage location with enough free space to hold a temporary backup archive if you plan to convert an existing container
  • Comfort editing a container's configuration file directly, for the ID-mapping steps later in this guide

Step-by-Step Tutorial

Step 1: Check which mode an existing container is running in

Before changing anything, confirm what you're currently working with. From the Proxmox shell:

pct config 105 | grep unprivileged

If you see unprivileged: 1, the container is unprivileged. If the line is absent entirely, the container is privileged — Proxmox omits the flag rather than setting it to 0 in most configs. You can also see this in the GUI under the container's Options tab.

Step 2: Create a new container in either mode

Through the GUI, the Create CT wizard's General tab has an Unprivileged container checkbox, checked by default. Uncheck it only if you have a specific, known reason to.

From the command line, the flag is explicit either way:

# Unprivileged (default, recommended)
pct create 210 local:vztmpl/debian-13-standard_13.0-1_amd64.tar.zst \
  --hostname app-ct --memory 1024 --rootfs local-lvm:8 --unprivileged 1

# Privileged (only when you have a specific reason)
pct create 211 local:vztmpl/debian-13-standard_13.0-1_amd64.tar.zst \
  --hostname legacy-ct --memory 1024 --rootfs local-lvm:8 --unprivileged 0

Step 3: Convert an existing container from privileged to unprivileged

There is no in-place toggle for this — the UID ownership of every file inside the container's filesystem has to be remapped, and Proxmox handles that safely through a backup-and-restore cycle rather than editing a live filesystem in place.

# 1. Stop the container
pct stop 211

# 2. Back it up
vzdump 211 --storage local --mode stop

# 3. Restore it as unprivileged, into a new container ID
pct restore 212 /var/lib/vz/dump/vzdump-lxc-211-*.tar.zst \
  --storage local-lvm --unprivileged 1

# 4. Start and verify the new container
pct start 212
pct config 212 | grep unprivileged

Once you've confirmed container 212 boots correctly and the workload runs as expected, you can destroy the old privileged container 211 and, if it matters for your setup, rename or renumber the new one to take its place.

Step 4: Convert the other direction, privileged from unprivileged

The same backup-and-restore approach works in reverse — restore with --unprivileged 0 instead:

pct stop 210
vzdump 210 --storage local --mode stop
pct restore 213 /var/lib/vz/dump/vzdump-lxc-210-*.tar.zst \
  --storage local-lvm --unprivileged 0
pct start 213

Do this only when you've hit a concrete, specific limitation in unprivileged mode that you can't work around any other way — not as a default troubleshooting step when something inside the container isn't behaving.

Step 5: Map a specific host UID into an unprivileged container (when you need bind-mount ownership to match)

This is the step that trips people up most often, so it's worth walking through directly. Say you're bind-mounting a host directory owned by UID 1005 into an unprivileged container, and you need the container's own UID 1005 to see matching ownership rather than nobody.

First, stop the container, then edit its configuration:

pct stop 210
nano /etc/pve/lxc/210.conf

Add ID map lines that carve out an exception for that specific UID/GID, leaving the rest of the default range mapped as usual:

lxc.idmap: u 0 100000 1005
lxc.idmap: u 1005 1005 1
lxc.idmap: u 1006 101006 64530
lxc.idmap: g 0 100000 1005
lxc.idmap: g 1005 1005 1
lxc.idmap: g 1006 101006 64530

Then grant the root user on the host permission to actually use that UID/GID, by adding a line to both /etc/subuid and /etc/subgid:

echo "root:1005:1" >> /etc/subuid
echo "root:1005:1" >> /etc/subgid

Finally, make sure the host-side directory you're bind-mounting is actually owned by that UID before you start the container back up:

chown -R 1005:1005 /path/on/host
pct start 210

Inside the container, that specific UID will now show correct, matching ownership on the bind-mounted path, while everything else in the container is still remapped through the normal unprivileged range.

Commands Explained

Command / Config LineWhat it does
pct config <id> | grep unprivilegedShows whether a container is running in unprivileged mode
pct create ... --unprivileged 1Creates a new container with UID/GID remapping enabled (default, recommended)
pct create ... --unprivileged 0Creates a new privileged container with no UID remapping
vzdump <id> --storage <name> --mode stopBacks up a container to an archive, stopping it first for a consistent snapshot
pct restore <new-id> <archive> --storage <name> --unprivileged <0|1>Restores a backup as a new container, choosing the privileged mode explicitly regardless of the original's mode
lxc.idmap: u <first> <last> <count>Defines a UID mapping range in a container's config; multiple lines carve out exceptions within the default range
/etc/subuid, /etc/subgidHost-level files granting root permission to use specific UID/GID ranges for container mappings

Common Errors

Files owned by "nobody" or "nogroup" inside a bind-mounted directory

This is the single most common surprise unprivileged mode produces. Every UID from the host gets remapped, and any UID that doesn't have an explicit mapping falls back to an unmapped, unrecognized identity inside the container. It's not a bug — it's the remapping working as designed. Fix it with the ID-map steps in Step 5, or reconsider whether that bind mount actually needs matching ownership at all.

"lxc-start: Permission denied" errors after adding a device or GPU passthrough entry

Certain low-level device access patterns assume the accessing UID matches the device's expected owner on the host, which unprivileged remapping breaks by default. Some cases can be worked around with careful cgroup device entries and matching idmap lines; others genuinely need a privileged container to function reliably.

NFS mount failures inside an unprivileged container

NFS servers frequently expect real, host-recognizable UIDs for permission checks, and an unprivileged container's remapped UIDs don't satisfy that from the NFS server's point of view. This is one of the more common legitimate reasons to fall back to a privileged container, or to mount the NFS share on the host and bind-mount it into the container instead.

Restore fails with an ID-mapping or "newuidmap" related error

This usually means the host's /etc/subuid or /etc/subgid ranges don't cover what the restore is trying to map. Check both files for a sane default range (Proxmox sets this up automatically on a standard install) and avoid manually shrinking them unless you know exactly why.

Troubleshooting

  • Not sure if a specific workload actually needs privileged mode? Try it unprivileged first. The overwhelming majority of container workloads — web apps, small databases, monitoring agents, reverse proxies — run with zero issues in the default unprivileged mode. Only reach for privileged once you've hit a concrete wall.
  • A container that used to work fine suddenly can't access a bind mount after a restore. Check whether the restore silently changed the container's privilege mode — restoring without specifying --unprivileged explicitly reuses whatever the backup recorded, but it's easy to assume otherwise when scripting restores.
  • Considering disabling AppArmor to fix a stubborn permission issue. Don't, unless you fully understand what you're giving up. Setting lxc.apparmor.profile: unconfined removes a real security layer on top of whatever mode the container is already in, and is almost never actually necessary to solve the underlying problem.
  • Need nesting (containers inside containers, or tools expecting direct namespace access) inside an unprivileged container. Enable the Nesting feature under the container's Options tab, or add features: nesting=1 to its config — but test thoroughly, since nested behavior differs meaningfully from what the same tooling does inside a privileged container.

Best Practices

Treat unprivileged as the non-negotiable default, and treat every privileged container on your cluster as something you should be able to explain in one sentence — "this one needs raw device access for X" or "this one runs software I already trust as much as the host." If you can't produce that sentence for a given container, it's worth revisiting whether it should be privileged at all.

Keep a running list, even an informal one, of which containers on your cluster are privileged and why. It's easy to flip the checkbox once to unblock something, forget about it, and end up with a handful of forgotten privileged containers years later with no memory of the original justification.

Whenever you find yourself fighting a permission issue in an unprivileged container, resist reaching for privileged mode as the fast fix. Nine times out of ten, the actual fix is a targeted lxc.idmap entry for the specific UID that's causing trouble, which solves the problem without giving up the security benefit for the entire container.

If a workload absolutely requires privileged mode, isolate it further where you can — a dedicated node, tighter firewall rules, or simply making sure nothing else sensitive shares storage or network segments with it. Privileged mode removes one layer of defense; it shouldn't be the only layer left standing.

Re-evaluate old privileged containers whenever you upgrade Proxmox VE major versions. Kernel and LXC feature improvements over time occasionally close the gap that originally forced a workload into privileged mode, and it's worth periodically checking whether an old exception can finally become unprivileged.

Frequently Asked Questions

Is unprivileged really the right default for almost everything?

Yes. It's been the Proxmox default since version 4.4 for a reason, and the LXC project's own security stance backs that up directly — unprivileged containers are considered safe by design, while privileged ones are explicitly not.

Does switching to unprivileged mode hurt performance?

No. The UID/GID remapping unprivileged mode relies on is a bookkeeping layer in the kernel's user namespace handling, not a performance-costing translation layer. You won't notice any difference in CPU, memory, or I/O throughput between the two modes.

Can I just disable AppArmor instead of dealing with unprivileged mode's restrictions?

You can, but you shouldn't treat it as a substitute. AppArmor and the privileged/unprivileged distinction are separate, complementary security layers — removing one to work around the other trades a targeted fix for a much broader loss of protection.

 

What actually happens during the "escape" scenario people talk about?

A kernel bug, a misconfigured mount, or a bug in LXC itself lets a process inside the container step outside its assigned namespace. In a privileged container, that process is root on the host the instant it escapes. In an unprivileged container, it's still an unmapped, low-privilege UID on the host — the escape still happened, but it didn't hand over the keys to everything else running on that Proxmox node.

Do backups behave differently for privileged versus unprivileged containers?

The backup and restore mechanics themselves are the same either way. What changes is that a restore lets you choose the target mode explicitly with --unprivileged, independent of what the original container was — which is exactly the mechanism this guide uses to convert between the two.

Is there a way to run Docker inside an unprivileged container without switching to privileged?

Generally yes, with the nesting feature enabled and some additional configuration, though it takes more setup than it would in a privileged container. Most people find it worth the extra effort rather than giving up the security benefits of unprivileged mode for an entire container just to simplify Docker's setup.

Conclusion

Privileged and unprivileged containers aren't really two flavors of the same feature — they're two different trust models wearing the same pct create command. Unprivileged mode's UID remapping is what turns a container escape from "root on your hypervisor" into "an anonymous, powerless process," and that difference is worth the occasional friction it introduces. Keep unprivileged as your default, understand exactly why you're reaching for privileged mode on the rare occasions you do, and use the ID-mapping and conversion steps here to solve permission problems surgically instead of reaching for the broader, riskier fix out of convenience.