Blog ·
Unprivileged is a measurement, not a setting
Restoring a container backup starts with reading the configuration inside the archive, because the safe value for one flag cannot be sent blindly.
By Provibr
Restoring a container on Proxmox is the same API call as creating one. You post to the container endpoint on a node, hand it the archive, and add the flag that turns creation into a restore. The virtual machine path does not even have that flag: there the archive itself is the restore. That symmetry is convenient and it hides a trap, because one of the fields in that call describes a security property that already has a value inside the archive.
Send the safe value and you may break the container. Leave the field out and you may put a privileged container into a fleet that is supposed not to have any. The only correct move is to read the archive first, and then act on what it says.
Two endpoints that look alike
The virtual machine and the container path differ in more places than the name suggests:
| Virtual machine | Container | |
|---|---|---|
| Endpoint | POST /nodes/{node}/qemu | POST /nodes/{node}/lxc |
| The archive | archive=<volid> | ostemplate=<volid> plus restore=1 |
| The name | name= | hostname= |
| The target storage must accept | images | rootdir |
| Task type in the task identifier | qmrestore | vzrestore |
Two of those rows are easy to get wrong in a way that produces a confusing error. Proxmox calls a container archive an operating system template, because the same parameter accepts both, and for a virtual machine the presence of archive is itself the restore, so no separate flag exists. And images and rootdir are two different content types: a storage can offer one without the other, so requiring images everywhere rejects perfectly good container storage, while not checking at all hands the customer a sentence about container directories that means nothing to them.
Which of the two kinds this is gets settled once. It is read off the archive filename at the moment the archive is listed, kept with the restore point, and sent along with the instruction; the agent picks its endpoint from that word and has no fallback. It has to work that way round, because a 404 from Proxmox does not distinguish a guest of the other kind from a guest that no longer exists. Trying the other endpoint after a 404 would let a deleted virtual machine be addressed as a container, and on a cluster where that number has since been reissued the wrong guest gets touched.
One flag is never sent at all, and it is called force. This is the additive restore: a machine appears alongside the original and the original keeps running. force is the flag with which Proxmox overwrites an existing guest, it exists on both endpoints, and it belongs to the other kind of restore, which has its own message, its own permission and a typed confirmation. Leaving it permanently out of this code path is cheaper than remembering not to set it.
One flag is always sent, and it is called unique. Without it, the restored guest inherits the hardware addresses of the original network cards, and two machines answering to the same address is an outage on the machine you were being careful not to touch.
Three refusals before anything is written
Each preflight exists because the alternative is a quote from Proxmox that a customer cannot act on. The archive has to still be there, because the platform picks it from an index that is by construction an observation of the past. The target number has to be free, and free has to mean free on the hypervisor rather than free in our own administration: the platform allocates inside the range it manages, so a guest that somebody created by hand is entirely real and entirely absent from every record we keep. And the target storage has to accept the right content type.
For a container there is a fourth, and it is the one this article is about.
Three things you can do with a privileged archive, and two are silently wrong
Unprivileged containers are the safe default, and the platform has no way to express anything else. So when an archive turns out to hold a privileged container, the agent has exactly three options.
The first is to send the unprivileged flag anyway. Proxmox then applies an identity mapping over the restored filesystem; its own parameter description says the field should not be modified by hand. The restore succeeds, the task reports fine, and the container does not work.
The second is to omit the field. Proxmox then takes the value from the archive, and a privileged container joins the fleet: one that shares the host kernel with root privileges, which is the thing the policy exists to prevent.
The third is to refuse, with a sentence saying what is in the archive. That is what happens, and it happens before anything is written. No request to create the container is sent at all.
Reading the configuration that is inside the archive
The measurement is one call to the extract endpoint for the archive. Three details are worth knowing.
The answer is text and not JSON. It is the container configuration file, one key and value per line, exactly as it sits on disk, so it needs its own small parser next to the one that handles the API's JSON.
And the absence of the key means the opposite of what absence usually means. Proxmox writes the unprivileged line only when it is true. A configuration without it describes a privileged container. Any reader that treats "not found" as "unknown, probably fine" waves through precisely the case the check was built for, so the comparison is generous about what it accepts as true and refuses everything else.
If the configuration cannot be read at all, the restore is refused as well, and the quote from Proxmox travels back with it. That is usually a permissions message, because the extract endpoint wants backup rights on the guest and allocation rights on the backup storage. Failing closed here has a real cost: an API token that is missing one permission cannot restore a container, and the reason is a check rather than a limitation. It is still the right side to fail on, because the open question behind it has two silently wrong answers.
The same read answers a second question
The configuration inside the archive also says which mount points were part of it, and Proxmox's rules there are not the ones people expect:
| What the configuration says | In the archive? | Why |
|---|---|---|
| The root filesystem | Always | It has no backup option at all; the field is not in its schema |
| A managed volume with the backup option on | Yes | Explicitly enabled |
| A managed volume without it | No | The option has to be turned on for a mount point to be included |
| A directory from the host | No | The contents of bind mount points are not backed up |
| A block device from the host | No | The same, for device mount points |
The third row is the dangerous one. On a virtual machine disk, excluded is the exception and included is the default; on a container mount point it is the other way round. Getting that backwards produces a coverage table that says "in the backup" about the most common configuration there is, which is worse than having no table, because it is a promise.
Snapshot sections in the configuration are skipped. A container with five snapshots would otherwise contribute five versions of the same root filesystem, each from an arbitrary moment.
One call serves both questions rather than two, because two calls could describe two different archives. And the verdict is computed on the platform, not by the agent: the agent reports the lines as it found them, and there is no field in which it could assert full coverage even if it wanted to. Demonstrably outside the backup beats not measured, so a container with one bind mount and one unreadable line is partial rather than unknown, and the unreadable line keeps its own word in the table.
There is a flag saying whether the configuration was read at all, and it is not decoration. An agent that could not read it returns an empty list, and an empty list is indistinguishable from a container with no mount points, except that the second does not exist.
When nobody can say what happened
A restore that starts and then loses its answer, because the agent restarted or the connection dropped, must not be guessed at. There are three sources of evidence: the archive index, the machine that was supposed to appear, and the status of the Proxmox task. Only the third can establish a failure; the other two can only confirm a success, because a missing archive and a missing machine are both equally consistent with a restore that is still running.
That reasoning happens on the platform. The agent's task lookup only carries the quote from Proxmox back and draws no conclusion of its own, because deciding what the three sources add up to needs the run and the intent it started from side by side, and only the platform has both.
Nothing is retried forever. There is a counter with a ceiling of eight attempts, and at the default interval of six hours that works out to about two days. Attempts where the task was demonstrably still running do not count against it, because a large backup over a slow network is allowed to take a night. When the counter runs out the run becomes unknown, and never failed. Failed would be a claim nobody verified, and completed would assert an archive nobody has seen.
What we would write down
- Do not send a value for a property that already exists in the input. Read it, then either confirm it or refuse.
- Find out what an absent key means before writing the parser. Here it means the unsafe value, which is the opposite of the usual convention.
- Fail closed when the measurement is unavailable, and say out loud what that costs, because somebody will hit it.
- Make one read answer every question it can. Two calls can describe two different states.
- Put the judgement where the context is. An observer that cannot express a verdict cannot get one wrong.
- Give an exhausted retry its own terminal word. Unknown is honest; failed and completed are both assertions nobody made.
For clarity about what this is: customer backups sit behind a rollout flag that is off by default, so none of this is purchasable today. And the important limit on the evidence: no real backup or restore has been run against a live hypervisor. The parameter behaviour above was read out of Proxmox's own source and documentation, and the code was exercised against a stand in server that answers the same endpoints. The extract call has also not been tried against an archive on a dedicated backup server, only against file based storage.
From the backup work on Provibr. Why an archive nobody has restored is not yet a backup is in a backup that was never restored, what the different backup modes actually guarantee is in Proxmox vzdump modes, and why reinstalling a container is a destroy and a rebuild is in reinstalling a container is a replace.