A running VM moved between two Proxmox nodes in 5 seconds with 5 ms of downtime, using one command, qm migrate 100 pve-b --online. Most of the other commands in this Proxmox CLI cheat sheet are just as short. The hard part is knowing which tool owns which job, and which commands quietly change the cluster when you only meant to read it.
The sheet covers qm for VMs, pct for LXC containers, pvesm and pveam for storage and templates, vzdump for backups, pvecm for the cluster, ha-manager for HA and pvesh for the REST API. We ran each command on a three-node cluster on Proxmox VE 9.2 (tested November 2026 on Debian 13 nodes with NFS shared storage), and the output shown is what those nodes printed, with only IP and MAC addresses replaced. The full reference is also a one-page PDF.
Proxmox CLI cheat sheet: quick reference
Every Proxmox tool below runs as root on a node, in an SSH session or in the node shell from the web UI. The split that matters is scope. The VM and container tools only see guests on the node you are logged into, while the cluster, HA and API tools see every node, so a command that works on one node can fail on the next for no reason other than where the guest lives. Commands that delete data or change cluster state carry a warning in the detailed sections.
| Tool | Manages | Scope |
|---|---|---|
qm | KVM virtual machines: create, start, config, disks, snapshots, migration | Local node |
pct | LXC containers: create, exec, config, mount points, restore | Local node |
pvesm / pveam | Storage definitions and content, container templates | Cluster config, per-node status |
vzdump / qmrestore | Backups and restores of VMs and containers | Guests on the local node |
pvecm | Cluster create, join, status, quorum, node removal | Cluster |
ha-manager | HA resources, HA migration, maintenance, disarm and arm | Cluster |
pvesh | Any REST API path: get, set, create, delete | Cluster |
Node and version
| Command | What it does |
|---|---|
pveversion | Manager version and running kernel |
pveversion -v | Every Proxmox package version |
pvesh get /version | Release and repo ID through the API |
pvesh get /cluster/status | Nodes, IPs, online and quorate flags |
qm: list, start and stop VMs
| Command | What it does |
|---|---|
qm list | VMs on this node with status, memory and PID |
qm start 100 | Start a VM |
qm status 100 --verbose | Status plus QEMU version, memory, uptime |
qm shutdown 100 --timeout 10 --forceStop 1 | Clean shutdown, hard stop after 10 s |
qm stop 100 | Pull the plug immediately |
qm agent 100 ping | Is the guest agent answering? |
qm guest cmd 100 network-get-interfaces | Guest IPs through the agent |
qm: create and configure
| Command | What it does |
|---|---|
qm create 100 --name web01 --memory 1024 --cores 1 --scsi0 shared:8 --net0 virtio,bridge=vmbr0 | New VM with an 8 GiB disk |
qm config 100 | Saved config |
qm set 100 --memory 2048 --onboot 1 | Change settings |
qm set 100 --startup order=1,up=30 | Boot order and delay on node start |
qm pending 100 | Changes waiting for a restart |
qm set 100 --ipconfig0 ip=10.0.1.51/24,gw=10.0.1.1 | Change a cloud-init VM IP |
qm cloudinit update 100 | Regenerate the cloud-init drive |
qm cloudinit dump 100 network | Network config the guest will get |
qm disk resize 101 scsi0 +4G | Grow a disk |
qm disk move 100 scsi0 shared --format qcow2 --delete 1 | Move or convert a disk |
qm unlock 101 | Clear a stale lock |
qm: snapshots, clones, migration, delete
| Command | What it does |
|---|---|
qm snapshot 100 pre-upgrade | Take a snapshot |
qm listsnapshot 100 | Snapshot tree |
qm rollback 100 pre-upgrade | Return to a snapshot |
qm clone 100 9000 --name tpl-debian --full 1 | Full copy |
qm template 9000 | Turn a VM into a template |
qm clone 9000 101 --name web02 | Linked clone of a template |
qm migrate 100 pve-b --online | Live-migrate to another node |
qm enroll-efi-keys 102 | Add 2023 Microsoft UEFI certs (9.1+, VM off, suspend BitLocker first) |
qm destroy 102 --purge 1 --destroy-unreferenced-disks 1 | DELETE the VM and its disks |
pct: LXC containers
| Command | What it does |
|---|---|
pct list | Containers on this node |
pct create 200 shared:vztmpl/debian-13-standard_13.6-1_amd64.tar.zst --hostname ct01 --rootfs shared:4 --net0 name=eth0,bridge=vmbr0,ip=dhcp --unprivileged 1 --start 1 | Create and start a container |
pct exec 200 -- hostname -I | Run one command inside |
pct enter 200 | Root shell inside the container |
pct set 200 --net0 name=eth0,bridge=vmbr0,ip=10.0.1.60/24,gw=10.0.1.1 | Change the container IP (live) |
pct set 200 --nameserver 10.0.1.1 --searchdomain lab.local | DNS, applied on next start |
pct set 200 --memory 1024 --cores 2 | Resources |
pct resize 200 rootfs +2G | Grow the root disk online |
pct df 200 | Disk usage per mount point |
pct push 200 /tmp/ntp.conf /etc/ntp-extra.conf | Copy a file in |
pct pull 200 /etc/os-release /tmp/ct200-os-release | Copy a file out |
pct shutdown 200 --timeout 30 | Clean shutdown |
pct stop 200 | Hard stop |
pct destroy 210 --purge 1 | DELETE the container |
pvesm and pveam: storage
| Command | What it does |
|---|---|
pvesm status | Every storage, type, state, usage |
pvesm scan nfs 10.0.1.11 | Exports an NFS server offers |
pvesm add nfs shared --server 10.0.1.11 --export /srv/nfs --content images,rootdir,backup,iso,vztmpl | Add shared NFS storage |
pvesm list shared --content backup | Backups on one storage |
pvesm set shared --disable 1 | Disable a storage |
pvesm free shared:210/vm-210-disk-0.raw | Delete one volume |
pveam update | Refresh the template index |
pveam available --section system | Downloadable OS templates |
pveam download shared debian-13-standard_13.6-1_amd64.tar.zst | Fetch a template |
pveam list shared | Templates on a storage |
vzdump: backup and restore
| Command | What it does |
|---|---|
vzdump 100 --storage shared --mode snapshot --compress zstd | Live VM backup |
vzdump 200 --storage shared --mode stop --compress zstd | Container backup, stopped |
vzdump 100 --storage shared --prune-backups keep-last=1 --remove 1 | Back up, then PRUNE old backups |
pvesm prune-backups shared --vmid 100 --keep-last 1 --dry-run 1 | Preview what a prune keeps |
qmrestore shared:backup/vzdump-qemu-100-....vma.zst 110 --storage shared --unique 1 | Restore a VM as a new ID |
pct restore 210 shared:backup/vzdump-lxc-200-....tar.zst --storage shared --unique 1 | Restore a container |
pvesh get /cluster/backup | Scheduled backup jobs |
pvecm: cluster
| Command | What it does |
|---|---|
pvecm create lab | Create a cluster on the first node |
pvecm add 10.0.1.11 --use_ssh 1 | Join this node to the cluster |
pvecm status | Quorum, votes, members |
pvecm nodes | Node IDs and names |
pvecm expected 1 | CHANGES expected votes (recovery only) |
pvecm delnode pve-c | REMOVE an offline node for good |
corosync-cfgtool -n | Corosync link state per node |
ha-manager: high availability
| Command | What it does |
|---|---|
ha-manager add vm:100 --state started --max_restart 1 | Make a VM an HA resource |
ha-manager status | CRM master, LRMs, services |
ha-manager config | HA resource settings |
ha-manager crm-command migrate vm:100 pve-c | Move an HA guest |
ha-manager crm-command node-maintenance enable pve-c | Drain a node |
ha-manager crm-command disarm-ha freeze | Release all watchdogs (9.2) |
ha-manager crm-command arm-ha | Re-arm HA |
ha-manager remove vm:100 | Stop managing a guest with HA |
pvesh and pveum: API and users
| Command | What it does |
|---|---|
pvesh ls /nodes/pve-a | Browse an API path |
pvesh get /cluster/resources --type vm --output-format json | Every guest in the cluster |
pvesh get /nodes/pve-a/status --output-format json | Node CPU, memory, kernel |
pvesh set /nodes/pve-a/qemu/101/config --tags staging | Write a config key |
pvesh create /nodes/pve-a/qemu/101/status/stop | Call an action, returns a task UPID |
pveum user add ops@pve --comment "Ops team" | Add a user, no password on argv |
pveum passwd ops@pve | Set the password at a prompt |
pveum acl modify /vms --users ops@pve --roles PVEVMUser | Grant a role on a path |
pveum user token add root@pam automation --privsep 0 | Token with FULL root rights (lab only) |
From here on, the page takes the same commands grouped by tool, with the output each one printed in our lab and the failures we hit on the way. Those failures are the useful part, because eight of the errors in the troubleshooting section came from commands that look correct on paper. The VM examples assume the cluster from Step 8 and a shared storage named shared from Step 6, so build those first if you are following along on empty nodes. The lab had three nested nodes (pve-a, pve-b, pve-c) with 2 vCPUs and 4 to 5 GiB of RAM each, and one NFS export on pve-a as shared storage.
Step 1: Check the node version and list VMs
Start with the version, because the CLI changed in 9.2 and two features on this page do not exist on older nodes (HA disarm and arm, and idmap on container mount points). pveversion prints the manager build and the running kernel in one line, so it also tells you whether a node was rebooted after its last kernel update. Run it on every node before cluster work, because mixed versions are the most common reason a migration or a join fails.
pveversion
Our lab nodes report the 9.2 manager on the 7.0 Proxmox kernel. If the kernel in the brackets is older than the newest proxmox-kernel package in the -v list, the node installed an update and is still waiting for its reboot, so this one line doubles as a reboot check across a cluster.
pve-manager/9.2.24/66bc407c6d90a67a (running kernel: 7.0.14-26-pve)
Add -v when a forum thread or support ticket asks for package versions. The full list runs to dozens of lines, so we trimmed it to the first twelve, which already name the corosync and kernel builds.
pveversion -v | head -12
Cluster stack versions sit near the top, and those are the ones that must match across nodes before you join them. A join between nodes on different corosync or manager releases is the first thing support asks about, so compare this block on both sides before running pvecm add.
proxmox-ve: 9.2.0 (running kernel: 7.0.14-26-pve)
pve-manager: 9.2.24 (running version: 9.2.24/66bc407c6d90a67a)
proxmox-kernel-helper: 9.2.1
proxmox-kernel-7.0: 7.0.14-26
proxmox-kernel-7.0.14-26-pve-signed: 7.0.14-26
ceph-fuse: 19.2.6-pve4
corosync: 3.1.10-pve3
criu: 4.1.1-1
frr-pythontools: 10.6.1-1+pve3
ifupdown2: 3.3.0-1+pmx12
libjs-extjs: 7.0.0-9
libproxmox-acme-perl: 1.7.2
On these nodes that meant corosync 3.1.10 and the 7.0.14 kernel, and all three nodes were installed from the same repository on the same day, so their versions matched before the join in Step 8.
Proxmox CLI: list VMs and start one
qm list answers the most common query in this family, but only for the node you are on. A VM that runs on another cluster member does not appear at all, so a script that loops over qm list on one node silently misses most of a cluster. Step 10 lists guests through pvesh instead. Create a test VM on the shared storage first.
qm create 100 --name web01 --memory 1024 --cores 1 --cpu x86-64-v2-AES --scsihw virtio-scsi-single --scsi0 shared:8,discard=on --net0 virtio,bridge=vmbr0 --ostype l26 --agent enabled=1
Proxmox formats a sparse raw file on the NFS storage and attaches it as scsi0. Raw is the default on file storage, and that default comes back to bite in Step 3, because raw files on NFS cannot be snapshotted. Add ,format=qcow2 to the disk option if you want snapshots there.
Formatting '/mnt/pve/shared/images/100/vm-100-disk-0.raw', fmt=raw size=8589934592 preallocation=off
scsi0: successfully created disk 'shared:100/vm-100-disk-0.raw,discard=on,size=8G'
Start the VM and check the list again. The PID column is the QEMU process on this node, which is the number you need for top -p or ps when a guest misbehaves. qm start itself prints nothing on success.
qm start 100
Now the list shows the VM as running with its QEMU PID. The memory column is the configured 1024 MB, not what the guest uses, so it says nothing about memory pressure on the node.
VMID NAME STATUS MEM(MB) BOOTDISK(GB) PID
100 web01 running 1024 8.00 3902
For more than running or stopped, qm status --verbose pulls live data from QEMU. The fields we kept are the ones worth reading. running-qemu and running-machine matter after an upgrade, because a VM keeps the old QEMU binary until it is stopped and started again, so a node can run the new package and still host guests on the old one.

Here the guest runs QEMU 11.0.3 with an i440fx 11.0 machine type and 1 GiB of RAM, of which the empty VM touched about 30 MiB. A VM that shows the previous QEMU major after an upgrade is a candidate for a planned restart or a live migration, which also lands it on the new binary.
Proxmox CLI: stop or shut down a VM
qm shutdown asks the guest to power off. With agent: enabled=1 in the config, Proxmox first pings the guest agent for about 3 seconds and uses it if it answers. If it does not, Proxmox prints a warning and falls back to an ACPI power button press. The timeout then measures how long the guest takes to act on ACPI. Our test VM had no operating system, so nothing inside it handled the button, and the first attempt spent 23.7 seconds (the agent ping plus the 20-second timeout) before it exited with code 255.
time qm shutdown 100 --timeout 20
The first line is the agent warning and the second is the real failure, so the message is easy to misread as an agent problem. A normal Linux or Windows guest without the agent still shuts down through ACPI. A guest that ignores ACPI (no OS, a hung kernel, a minimal image without acpid) leaves the VM running, as here.
QEMU Guest Agent is not running - VM 100 qga command 'guest-ping' failed - got timeout
VM quit/powerdown failed - got timeout
real 0m23.711s
user 0m0.607s
sys 0m0.081s
Three ways out exist, and they differ in how much the guest gets to clean up. Pick by what runs inside the VM, because a forced stop on a database is a crash as far as the database knows.
--forceStop 1: keeps the polite attempt and turns the timeout into a hard stop. This is the form for scripts and maintenance runbooks.qm stop 100: skips the guest entirely, the same as pulling the power cable. Use it on a hung VM, never on a database you care about.- Install
qemu-guest-agentin the guest: gives Proxmox a clean shutdown path that does not depend on ACPI, plus filesystem freeze during backups and guest IPs inqm guest cmd.
The forced variant stopped the same VM in 14.8 seconds, about 10 of them spent on the timeout. Set the timeout to what the slowest service in the guest needs to stop, not to the smallest number that works on an idle VM.

qm agent 100 ping tells you which state a VM is in before you plan a shutdown. On this VM it returned QEMU guest agent is not running, the same message qm guest cmd prints, so any command that depends on the agent (guest IPs, filesystem freeze during backup) will fail here too.
Step 2: Change VM settings and the VM IP address
qm set writes the VM config, and anything the running VM cannot hot-plug becomes a pending change. Memory without hotplug is one of them, so we changed it together with two keys that apply at once.
qm set 100 --memory 2048 --onboot 1 --startup order=1,up=30
qm config --current shows the running values, so it is the right check after any change that might be pending. Plain qm config shows pending values as if they were already live, so it would report 2048 here.
qm config 100 --current | grep -E "memory|onboot|startup"
onboot and startup applied immediately, but the running VM still reports 1024 MiB.
memory: 1024
onboot: 1
startup: order=1,up=30
qm pending 100 lists the full config with a new memory: 2048 line next to the current value. That line stays until the VM is stopped and started again. A reboot from inside the guest is not enough, because QEMU itself has to restart to change the RAM size. Per-VM boot order lives in --startup order=1,up=30 on each guest, and there is no node-level startup-order command (more on that in the last section).
Proxmox CLI: change a VM IP with cloud-init
On a cloud-init VM the IP address is a VM option, not something you edit in the guest. We added the cloud-init drive and set the first interface in one call, which creates a 4 MiB qcow2 drive on the storage and generates the ISO the guest reads at boot.
qm set 100 --ide2 shared:cloudinit --ciuser admin --ipconfig0 ip=10.0.1.50/24,gw=10.0.1.1 --nameserver 10.0.1.1
Proxmox confirms the new drive and the ISO generation. The drive is tiny, but it is still a volume on the storage, so it follows the VM through clones, backups and migrations like any other disk.
update VM 100: -ciuser admin -ide2 shared:cloudinit -ipconfig0 ip=10.0.1.50/24,gw=10.0.1.1 -nameserver 10.0.1.1
Formatting '/mnt/pve/shared/images/100/vm-100-cloudinit.qcow2', fmt=qcow2 cluster_size=65536 extended_l2=off preallocation=metadata compression_type=zlib size=4194304 lazy_refcounts=off refcount_bits=16
ide2: successfully created disk 'shared:100/vm-100-cloudinit.qcow2,media=cdrom'
generating cloud-init ISO
Changing the address later is the same option with a new value, followed by a regeneration of the drive. Skipping the second command is harmless on a stopped VM, since Proxmox regenerates the ISO at start, but on a running VM the old file stays in place until then.
qm set 100 --ipconfig0 ip=10.0.1.51/24,gw=10.0.1.1
qm cloudinit update 100
Before booting, check what the guest will receive. qm cloudinit dump prints the generated network config, which catches a wrong prefix or gateway before the VM drops off the network. The dump below was taken after the first ipconfig0 value, so it shows the .50 address.
qm cloudinit dump 100 network
Subnet, gateway and nameserver all match the values passed to qm set. If a dump still shows an old address, the drive was not regenerated, so run qm cloudinit update before you start the VM.
version: 1
config:
- type: physical
name: eth0
mac_address: 'bc:24:11:00:00:01'
subnets:
- type: static
address: '10.0.1.50'
netmask: '255.255.255.0'
gateway: '10.0.1.1'
- type: nameserver
address:
- '10.0.1.1'
The guest applies the new address on its next boot, because cloud-init reads the drive only at start. For a VM without cloud-init, the IP is changed inside the guest OS like on any other machine, and Proxmox only learns it through the guest agent.
Step 3: Snapshot, clone and migrate VMs
Snapshots depend on the disk format and the storage, not on qm. A raw file on NFS cannot be snapshotted, and our first attempt failed with a message that does not name the cause. Check qm config for the disk line first, because the volume name ends in .raw or .qcow2 and that suffix decides the answer on file storage.
qm snapshot 100 pre-upgrade --description "before kernel update"
One line came back, with exit code 255. Nothing in it mentions the raw format, so the cause has to come from the storage type, because file-based storages only snapshot qcow2 images.
snapshot feature is not available
Converting the disk to qcow2 on the same storage fixes it. qm disk move copied the 8 GiB image and, with --delete 1, removed the raw source after a successful copy. The same snapshot command then worked and qm listsnapshot showed the tree with the description we passed.

Rollback is immediate. We changed the core count to 4 after the snapshot, rolled back, and the config returned to 2 cores with parent: pre-upgrade. Storage backends that snapshot natively (ZFS, LVM-thin, Ceph RBD) accept raw disks, so the qcow2 step only applies to file-based storage. The full comparison is in our Proxmox storage types guide, and the snapshot workflow itself is covered in managing Proxmox VM snapshots.
Clone a VM and build a template
A full clone copies every disk, which took about 10 seconds for the 8 GiB image here. A template plus linked clones shares the base image, so each new VM costs only its own changes. We converted the full clone into template 9000. On NFS the template step prints a warning, because Proxmox tries to set the immutable flag on the base image and NFS does not support chattr.
qm template 9000
Exit code is still 0, so a script that checks it will not stop here. Linked clones from this template worked normally, which makes the warning safe to ignore on NFS.
/usr/bin/chattr: Operation not supported while reading flags on /mnt/pve/shared/images/9000/base-9000-disk-0.qcow2
command '/usr/bin/chattr +i /mnt/pve/shared/images/9000/base-9000-disk-0.qcow2' failed: exit code 1
A clone from a template is linked by default. In the output below, the backing_file=../9000/base-9000-disk-0.qcow2 field confirms the new disk only stores differences. Linked clones are fast and small, but they tie every clone to the template, so a damaged base image damages them all. Use --full 1 for anything long-lived.
qm clone 9000 101 --name web02
Only the cloud-init drive is copied in full, while the system disk is linked, so each clone gets its own cloud-init settings and shares the 8 GiB base image.
creating a clone of VM 9000 with ID 101
create linked clone of drive scsi0 (shared:9000/base-9000-disk-0.qcow2)
clone 9000/base-9000-disk-0.qcow2: images, vm-101-disk-0.qcow2, 101 to vm-101-disk-0.qcow2 (base=../9000/base-9000-disk-0.qcow2)
Formatting '/mnt/pve/shared/images/101/vm-101-disk-0.qcow2', fmt=qcow2 cluster_size=131072 extended_l2=on preallocation=metadata compression_type=zlib size=8589934592 backing_file=../9000/base-9000-disk-0.qcow2 backing_fmt=qcow2 lazy_refcounts=off refcount_bits=16
Grow the clone’s disk with qm disk resize 101 scsi0 +4G. qm list then reported 12 GiB for web02 against 8 GiB for its template, but the guest still needs its partition and filesystem grown, which cloud images do on boot.
Live-migrate a VM to another node
With the disk on shared storage, live migration only copies RAM. The 2 GiB VM moved from pve-a to pve-b in 5 seconds with 5 ms of downtime, and only 7.1 MiB of VM state crossed the wire because the guest was idle. A busy guest dirties memory faster, so expect more passes and a longer run.

Local disks need --with-local-disks and take as long as the disk copy. After the move, every qm command for VM 100 must run on pve-b, because qm status 100 on pve-a now fails with a missing config file (see the errors section). Migration between separate clusters is a different procedure, covered in migrating VMs between Proxmox servers.
Enroll the 2023 UEFI certificates on a VM
qm enroll-efi-keys adds the 2023 Microsoft UEFI certificates to an EFI disk that was created with pre-enrolled keys, which matters for Secure Boot guests because the 2011 certificates expired in June 2026. The CLI command predates 9.2. It was posted to the pve-devel list in November 2025, and the 9.2 release notes describe it as existing and add the same action to the web UI and API. We created a fresh OVMF VM to see what it does on a new disk.
qm create 102 --name win-test --bios ovmf --machine q35 --efidisk0 shared:1,efitype=4m,pre-enrolled-keys=1 --memory 1024
The ms-cert=2023k marker on the new efidisk0 shows the 2023 certificates are already in the disk. Disks created on current packages carry ms-cert=2023k. Older disks, including ones marked ms-cert=2023 or 2023w, need the command.
Formatting '/mnt/pve/shared/images/102/vm-102-disk-0.raw', fmt=raw size=540672 preallocation=off
INFO: reading raw edk2 varstore from /usr/share/pve-edk2-firmware//OVMF_VARS_4M.ms.fd
INFO: var store range: 0x64 -> 0x40000
efidisk0: successfully created disk 'shared:102/vm-102-disk-0.raw,efitype=4m,ms-cert=2023k,pre-enrolled-keys=1,size=528K'
Warning: on a Windows guest with BitLocker, suspend the protectors inside the guest first with manage-bde -protectors -disable C: for each encrypted drive. Changing the Secure Boot keys otherwise makes Windows ask for the BitLocker recovery key on the next boot, and a guest without that key on file is locked out. The VM must also be shut down while the command runs. On our fresh VM the enrollment was a no-op, and the command said so instead of failing.
qm enroll-efi-keys 102
On VM 102 it skips, because the marker is already present.
skipping - no pre-enrolled keys or already got ms-cert=2023k marker
A VM with an older EFI disk gets the certificates written and picks them up on its next start. To find the candidates, run qm config <vmid> | grep efidisk0 and look for disks that have pre-enrolled-keys=1 but no ms-cert=2023k. Disks with pre-enrolled-keys=0 are skipped as well, since there are no Microsoft keys on them to update.
Step 4: Delete a VM from the CLI
Warning: qm destroy removes the VM config and every disk it references. There is no trash and no confirmation prompt. Check the VMID twice with qm config before running it, and take a backup first if anything on the disks matters.
qm destroy 102 --purge 1 --destroy-unreferenced-disks 1
--purge 1 also removes the VMID from backup jobs, replication jobs and HA, and --destroy-unreferenced-disks 1 deletes disks on enabled storages that carry the VMID but are not in its config. That second flag is the one to think about, because a detached disk you kept on purpose (an old unused0 volume, for example) goes with the VM. Leave it off if you are not sure what is on the storage.
purging VM 102 from related configurations..
Without --purge, a destroyed VM stays listed in its backup job, so every scheduled run logs an error for it until someone edits the job. We confirmed the clean-up later in the lab: after qm destroy 100 --purge 1, the nightly-web backup job went from vmid 100,200 to 200 on its own. Templates with linked clones refuse to go.
qm destroy 9000
VM 101 still references the base image. Proxmox checks every VM config for that reference before it deletes a base volume, so this refusal is reliable.
base volume 'shared:9000/base-9000-disk-0.qcow2' is still in use by linked cloned
Destroy or full-clone the linked VMs first. Beyond that refusal and an active lock, the only guard is one you set yourself: qm set <vmid> --protection 1 blocks removal of the VM and its disks, so set it on anything that must not be deleted by a stray VMID in a script.
Step 5: Manage LXC containers with pct
pct mirrors qm for containers and adds four commands VMs do not have. They work because the container shares the host kernel, so the host can enter its namespaces directly instead of going through a network or an agent.
pct exec: runs one command inside and returns, which suits scripts.pct enter: opens a root shell inside, with no SSH or password because it attaches from the host.pct pushandpct pull: copy single files in and out without a network path to the container.
Containers start from a template, so pveam comes first (Step 6). With the Debian 13 template on the shared storage, one command created and started an unprivileged container in a few seconds, most of them spent extracting the 528 MiB template and generating SSH host keys.
pct create 200 shared:vztmpl/debian-13-standard_13.6-1_amd64.tar.zst --hostname ct01 --cores 1 --memory 512 --swap 512 --rootfs shared:4 --net0 name=eth0,bridge=vmbr0,ip=dhcp --unprivileged 1 --features nesting=1 --ssh-public-keys /root/.ssh/id_rsa.pub --start 1
Nothing in the create command set an address, so the container asked the LAN DHCP server through the bridge. pct exec shows what it got without logging in.
pct exec 200 -- hostname -I
One address came back, which confirms the veth pair, the bridge and the outside network in a single check.
10.0.1.85
Our walkthrough on running Debian and Ubuntu in LXC on Proxmox covers the template choices in more depth, including when a privileged container is worth the weaker isolation. Stay unprivileged unless a workload proves it needs more, because a root escape from a privileged container is root on the node.
Proxmox CLI: change a container IP
Container networking is part of the container config, so changing the IP is a pct set on net0. It applied to the running container immediately, without a restart, which also means a typo cuts the container off at once. Keep a console open with pct enter when you change the address of a container you reach over the network.

DNS behaves differently. pct set 200 --nameserver 10.0.1.1 --searchdomain lab.local saved the values at once, but /etc/resolv.conf inside the container still held the old resolver until pct reboot 200. After the reboot it contained the new nameserver between BEGIN PVE and END PVE markers. CPU limits followed the same rule, since nproc returned 1 right after --cores 2 and 2 after the reboot.
Mount a host directory with idmap
Bind mounts into unprivileged containers fail on permissions, because container root is host UID 100000. We reproduced the classic error, touch: cannot touch '/data/hello': Permission denied. The usual fix is chown 100000:100000 on the host directory. Proxmox VE 9.2 adds an idmap option to the mount point itself, which maps IDs for that one mount and leaves the rest of the container alone.
pct set 200 --mp0 '/srv/data,mp=/data,idmap=u:0:0:65536;g:0:0:65536'
After a container reboot, the same touch succeeded and the file appeared on the host owned by UID 0, as the screenshot above shows. That mapping lets container root write as host root inside /srv/data, so keep it to directories that hold nothing else, or map a narrower range such as a single service UID. Growing the container disk is online too: pct resize 200 rootfs +2G extended the filesystem while the container ran, and pct df 200 reported 5.8G with 571.1M used afterwards.
Step 6: Inspect storage with pvesm and pveam
pvesm status is the first command to run when a guest refuses to start, because a storage that is disabled or not online blocks every guest with a disk on it. A Debian-based install starts with only local, so we added an NFS export from pve-a as cluster-wide shared storage. pvesm scan shows what a server offers before you add it.
pvesm scan nfs 10.0.1.11
One export is offered to the lab subnet. An export missing here, or one restricted to another subnet, will also fail in pvesm add, so this is the cheaper place to find it.
/srv/nfs 10.0.1.0/24
Note that pvesm scan discovers targets and does not list volumes, a mix-up we found on at least one popular cheat sheet. Add the export with the content types it should hold and a retention policy for backups, since that policy is what every later vzdump run prunes against.
pvesm add nfs shared --server 10.0.1.11 --export /srv/nfs --content images,rootdir,backup,iso,vztmpl,import --prune-backups keep-last=3
Every node now shows the new storage as active, because storage definitions live in the cluster file system and pvesm add runs once for all of them. The total and used columns describe the NFS server’s filesystem, here the root disk of pve-a, which is why local and shared report nearly identical numbers.
Name Type Status Total (KiB) Used (KiB) Available (KiB) %
local dir active 41072836 5606356 33747568 13.65%
shared nfs active 41073664 5606400 33747968 13.65%
pveam handles container templates. Refresh the index, then filter it. The template index on our node listed arm64 builds next to amd64 ones, so read the architecture column before you download.
pveam available --section system | grep -E "debian|alpine|ubuntu"
Filtered for Debian, Alpine and Ubuntu, the index on the lab node listed these templates. The amd64 and arm64 rows share names apart from the suffix, so a name copied from a forum post can pull the wrong architecture.
system alpine-3.22-default_20250617_amd64.tar.xz amd64
system alpine-3.23-default_20260116_amd64.tar.xz amd64
system alpine-3.24-default_20260714_amd64.tar.xz amd64
system alpine-3.24-default_20260803_arm64.tar.xz arm64
system debian-12-standard_12.12-1_amd64.tar.zst amd64
system debian-13-standard_13.6-1_amd64.tar.zst amd64
system debian-13-standard_13.6-1_arm64.tar.zst arm64
system ubuntu-22.04-standard_22.04-1_amd64.tar.zst amd64
system ubuntu-24.04-standard_24.04-2_amd64.tar.zst amd64
system ubuntu-25.04-standard_25.04-1.1_amd64.tar.zst amd64
system ubuntu-26.04-standard_26.04-1_amd64.tar.zst amd64
pveam download shared debian-13-standard_13.6-1_amd64.tar.zst fetched the 124 MB template at 14.0 MB/s and verified its checksum. Templates on shared storage are downloaded once for the whole cluster, so put them there rather than on each node’s local. For storage design beyond a lab NFS export, see the 3-node hyperconverged Ceph build.
Step 7: Back up and restore with vzdump
vzdump backs up one guest or many, and the mode decides how much downtime the guest sees. It runs on the node that hosts the guest, so a backup of a VM that HA moved overnight has to run on its new node. Scheduled jobs handle that themselves, while a manual run does not.
snapshot: no downtime. VMs use QEMU’s own backup, so it works on any storage. Containers need a storage that can snapshot.suspend: containers only in practice. Two rsync passes with a short freeze between them.stop: the most consistent copy, at the cost of a full stop and start.
In snapshot mode the 8 GiB VM finished in 8 seconds, because QEMU skips unallocated blocks and the test disk was empty. We dropped the per-percent progress lines from the log below.
vzdump 100 --storage shared --mode snapshot --compress zstd --notes-template "{{guestname}} before upgrade"
Because the disk was empty, the archive is 278 KB. The run also applied the storage’s keep-last=3 retention on its own, so a storage-level policy covers manual backups as well as scheduled ones.
INFO: Starting Backup of VM 100 (qemu)
INFO: Backup started at 2026-11-01 10:23:41
INFO: include disk 'scsi0' 'shared:100/vm-100-disk-0.qcow2' 8G
INFO: backup mode: snapshot
INFO: snapshots found (not included into backup)
INFO: creating vzdump archive '/mnt/pve/shared/dump/vzdump-qemu-100-2026_11_01-10_23_41.vma.zst'
INFO: skipping guest filesystem freeze - agent configured but not running?
INFO: starting backup via QMP command
INFO: backup is sparse: 8.00 GiB (100%) total zero data
INFO: transferred 8.00 GiB in 5 seconds (1.6 GiB/s)
INFO: archive file size: 278KB
INFO: adding notes to backup
INFO: prune older backups with retention: keep-last=3
INFO: pruned 0 backup(s)
INFO: Finished Backup of VM 100 (00:00:08)
INFO: Backup finished at 2026-11-01 10:23:49
INFO: Backup job finished successfully
WARN: 'root@pam' does not have a configured email address in the user configuration - not sending an email to this user
ERROR: could not notify via target `mail-to-root`: could not notify via endpoint(s): mail-to-root: no recipients provided for the mail, cannot send it.
The mail-to-root error at the end is cosmetic. root@pam has no email address, so the notification has nowhere to go. Set one with pveum user modify root@pam --email [email protected] or configure a notification target, otherwise every backup ends on an error line that hides real failures.
Containers on storage without snapshots fall back to suspend mode, and that fallback exposed a measurement worth knowing. With the NFS export set to sync (the Debian default), the first rsync pass moved 546.67 MB in 535 seconds, about 1 MB/s. After switching the export to async, a direct write test reached 1.7 GB/s and the same container backed up in stop mode in 3.5 seconds.
time vzdump 200 --storage shared --mode stop --compress zstd
With tar instead of rsync, the stream ran at 210 MiB/s and compressed 558 MiB into a 160 MB archive. The container was already stopped, so stop mode skipped the shutdown step that had hung earlier.
INFO: Starting Backup of VM 200 (lxc)
INFO: Backup started at 2026-11-01 11:14:26
INFO: status = stopped
INFO: backup mode: stop
INFO: excluding bind mount point mp0 ('/data') from backup (not a volume)
INFO: creating vzdump archive '/mnt/pve/shared/dump/vzdump-lxc-200-2026_11_01-11_14_26.tar.zst'
INFO: Total bytes written: 584919040 (558MiB, 210MiB/s)
INFO: archive file size: 160MB
INFO: prune older backups with retention: keep-last=3
INFO: pruned 0 backup(s)
INFO: Finished Backup of VM 200 (00:00:03)
INFO: Backup finished at 2026-11-01 11:14:29
INFO: Backup job finished successfully
Async NFS acknowledges writes before they reach disk, so a server crash can lose the last seconds of a backup. On a dedicated backup target that trade is usually acceptable, and on VM disk storage it is not. Note also the excluding bind mount point mp0 line, because host directories mounted into a container are never in its backup.
Restore a VM or container
qmrestore restores VM archives and pct restore restores container archives. --unique 1 generates new MAC addresses, so the restored copy can run next to the original without an address clash. The VM restore took seconds because the archive was sparse, and the container restore read 558 MiB at 412 MiB/s.

Restoring onto an existing VMID requires --force 1 and overwrites that guest, so restore to a free ID unless you mean to replace it. For deduplicated, incremental backups across many hosts, point vzdump at a Proxmox Backup Server datastore instead of NFS.
Prune old backups and schedule jobs
Warning: --remove defaults to 1, so every vzdump run that has a retention policy deletes backups outside it. A one-off vzdump with a tight --prune-backups can wipe the history you were relying on, and we lost the annotated backup from earlier this way.
vzdump 100 --storage shared --mode snapshot --prune-backups keep-last=1 --remove 1 2>&1 | grep -E "prune|Finished|archive"
Watch the prune line, because it is the only place the run admits that it deleted something. The exit code and the task status look the same whether a backup pruned or not.
INFO: starting new backup job: vzdump 100 --remove 1 --mode snapshot --storage shared --prune-backups 'keep-last=1'
INFO: creating vzdump archive '/mnt/pve/shared/dump/vzdump-qemu-100-2026_11_01-11_14_59.vma'
INFO: archive file size: 1MB
INFO: prune older backups with retention: keep-last=1
INFO: pruned 1 backup(s) not covered by keep-retention policy
INFO: Finished Backup of VM 100 (00:00:07)
After this run, the backup that carried the web01 before upgrade note was gone. Preview a policy with pvesm prune-backups shared --vmid 100 --keep-last 1 --dry-run 1, which marks each archive keep or remove without touching anything. Scheduled jobs live in the cluster config and are created through the API. 9.2 deprecates the old starttime and dow fields in favour of schedule.
pvesh create /cluster/backup --id nightly-web --schedule "sat 02:00" --storage shared --vmid 100,200 --mode snapshot --compress zstd --prune-backups keep-daily=7,keep-weekly=4 --enabled 1
systemd-analyze calendar "sat 02:00" is a quick way to check what a schedule string means before saving it. On the lab it returned the next two Saturdays at 02:00 UTC, which matched the next-run timestamp the job reported.
Step 8: Build and inspect a cluster with pvecm
pvecm create runs once, on the first node. Every other node joins with pvecm add pointed at an existing member, and --use_ssh 1 makes the join use root SSH instead of an API password prompt. A joining node must have no guests of its own, because its /etc/pve is replaced by the cluster’s copy during the join.
pvecm add 10.0.1.11 --use_ssh 1
Joining copies the corosync key, waits for quorum and issues the node a new certificate. It also merges the authorized SSH keys, so after the join every node reaches every other node as root, which migration relies on.
No cluster network links passed explicitly, fallback to local node IP '10.0.1.13'
copy corosync auth key
stopping pve-cluster service
backup old database to '/var/lib/pve-cluster/backup/config-1793528144.sql.gz'
waiting for quorum...OK
(re)generate node files
generate new node certificate
merge authorized SSH keys
generated new node certificate, restart pveproxy and pvedaemon services
successfully added node 'pve-c' to cluster.
The first join attempt on each node failed with unable to copy ssh ID: exit code 1, because the joining node had never seen the target’s SSH host key. Accepting the key once (ssh [email protected] hostname) fixed it. With three nodes joined, pvecm status shows each one with a vote and the quorum threshold of 2.

Three nodes is the smallest size that survives the loss of one, because two of three votes still form a majority. Hardware for a cluster like this does not have to be expensive, and our mini PC picks for a Proxmox homelab are sized for exactly this layout.
What pvecm expected really changes
pvecm expected is not a status command. Without an argument it fails with 400 not enough arguments, and with one it tells corosync a new expected vote count. On a healthy cluster corosync refuses a value that would drop quorum below a majority of the votes present.
pvecm expected 1
Three votes are present, so a value of 1 is rejected. Nothing changed, and pvecm status still showed expected votes at 3 afterwards.
Unable to set expected votes: CS_ERR_INVALID_PARAM
Its real job is recovery. We stopped corosync on pve-b and pve-c, which left pve-a with 1 vote out of 3. The node blocked every write, so qm start refused and even touch /etc/pve/test.txt returned Permission denied, because /etc/pve goes read-only without quorum. pvecm expected 1 made the single node quorate again.

Use it only when you know the other nodes are down and will stay down while you work, because two partitions that both believe they are quorate can start the same guest twice. When pve-b and pve-c rejoined, expected votes returned to 3 on their own, so there is nothing to undo afterwards.
Remove a node from the cluster
Warning: pvecm delnode is permanent. Power the node off first and never bring it back on the same network with its old cluster config, or it will try to rejoin with stale state. With pve-c off, the removal printed a harmless kill error (the node was already gone) and the cluster dropped to two votes.
pvecm delnode pve-c
Node 3 leaves the corosync config even though the kill step complains. Removing it also lowered expected votes to 2, so the remaining pair stayed quorate.
Killing node 3
Could not kill node (error = CS_ERR_NOT_EXIST)
Two things remain after the command. /etc/pve/nodes/pve-c is still there and has to be removed by hand once you have copied any guest configs you need from it. And a two-node cluster needs both votes for quorum, so one node failing stops all writes. Add a QDevice or a third node before you rely on it.
Step 9: Run HA resources with ha-manager
ha-manager add puts a guest under the cluster resource manager, which restarts or relocates it when its node fails. Fifteen seconds after the add, the LRM on the VM’s node was active with its watchdog armed, which is the moment that node becomes able to fence itself.
ha-manager add vm:100 --state started --max_restart 1 --max_relocate 1 --comment "web01 HA"
The add returns at once and nothing happens in the foreground, because the CRM picks the request up on its next cycle. Give it 10 to 20 seconds, then read the HA state.
ha-manager status
VM 100 sits on pve-b, whose LRM holds the active watchdog, while the master CRM runs on pve-c. The other LRMs are idle with their watchdogs in standby, so only a node that runs an HA guest can fence.
quorum OK
master pve-c (active, Sun Nov 1 10:34:07 2026)
fencing armed (CRM watchdog active)
lrm pve-a (idle, watchdog standby, Sun Nov 1 10:34:09 2026)
lrm pve-b (active, watchdog active, Sun Nov 1 10:34:10 2026)
lrm pve-c (idle, watchdog standby, Sun Nov 1 10:34:09 2026)
service vm:100 (pve-b, started)
Moving an HA guest goes through the CRM, not qm migrate. ha-manager crm-command migrate vm:100 pve-c queued the move and the service showed starting on pve-c about 25 seconds later. node-maintenance enable pve-c then drained the node and moved the VM back to pve-b, and disable returned the node to service.
Disarm HA before risky cluster work
An armed node whose LRM runs HA services fences itself when it loses quorum, because its watchdog reboots it after about 60 seconds. That is correct during a real failure and dangerous during planned network or corosync work. Proxmox VE 9.2 adds disarm-ha, which releases every watchdog in the cluster and takes one of two resource modes.
freeze: resources stay tracked, but new commands and state changes are not applied until you re-arm.ignore: resources leave HA tracking and can be managed withqmandpctas if HA were not there.
We used freeze, because the VM should stay where it was during the test. Twenty seconds after the command, every LRM reported disarm mode, watchdog released and the service state changed to freeze. Nothing restarts or relocates while the cluster is in this state, so keep the window short.

With HA disarmed, the quorum test in Step 8 ran without fencing any node, and ha-manager crm-command arm-ha restored the watchdogs afterwards. Step 11 shows what happens when you skip this, since a node IP change with HA armed came within 30 seconds of a fence.
Step 10: Query the API with pvesh
pvesh calls the same REST API the web UI uses, from the shell and without a token, because it runs as root on a node. It is the answer to listing every VM in the cluster, which qm list cannot do. The default table output is too wide for a terminal, so we asked for JSON and filtered it with jq, which is not on a stock node (apt install jq adds it).
pvesh get /cluster/resources --type vm --output-format json | jq -r '.[] | [.id, .node, .status, .name, .hastate // "-"] | @tsv'
Each guest appears once with its node, state and HA state, including the VM that HA had moved to pve-c. Tab-separated output drops straight into awk or a while read loop, so this one line replaces a loop over qm list on every node.
qemu/100 pve-c running web01 started
qemu/101 pve-a stopped web02 -
lxc/200 pve-a running ct01 -
qemu/9000 pve-a stopped tpl-debian -
Node health comes from /nodes/{node}/status. The lab node had 2 CPUs and about 4.8 GiB of RAM with 2.0 GiB in use. The same path works for any node from any node, so one monitoring script on one host can poll the whole cluster.
pvesh get /nodes/pve-a/status --output-format json | jq '{pveversion, kernel: ."current-kernel".release, cpus: .cpuinfo.cpus, mem_total: .memory.total, mem_used: .memory.used, uptime}'
Only six fields survive the jq filter out of a much larger object. Memory values are in bytes, so 5155221504 is about 4.8 GiB.
{
"pveversion": "pve-manager/9.2.24/66bc407c6d90a67a",
"kernel": "7.0.14-26-pve",
"cpus": 2,
"mem_total": 5155221504,
"mem_used": 2144948224,
"uptime": 1959
}
Writes map to HTTP verbs, so set is PUT, create is POST and delete is DELETE. Actions such as stop return a task UPID that you can follow with pvenode task status.
pvesh create /nodes/pve-a/qemu/101/status/stop
Proxmox returns the task ID straight away and runs the stop in the background, so a script that needs the VM down must poll the task or the VM status before its next step.
UPID:pve-a:00002D0F:0002F404:6AE716BB:qmstop:101:root@pam:
pvesh ls /nodes/pve-a lists the paths below a node with their allowed methods, which is faster than the API viewer when you need one endpoint. For automation from another machine, an API token (pveum user token add) and the Proxmox Ansible collection are the cleaner route, because root SSH to every node is not something to hand a CI system.
Step 11: Change a Proxmox node IP address
A standalone node needs two files changed, the bridge address in /etc/network/interfaces and the node’s line in /etc/hosts, followed by ifreload -a. A cluster member also needs its corosync address changed, and the order matters. This is the sequence we recommend after running the change on our cluster, where skipping the first step nearly cost us a node.
- Disarm HA with
ha-manager crm-command disarm-ha freeze(Step 9). - Edit a copy of
/etc/pve/corosync.conf, change the node’sring0_addr, raiseconfig_versionby one, then move the copy over the original. - Update the node’s line in
/etc/hostson every node. - Change the address in
/etc/network/interfaceson the moving node and runifreload -a. - Restart corosync on every node, then check
pvecm statusand re-arm HA.
Editing a copy means a half-written file is never live, because pmxcfs pushes corosync.conf to every node the moment it changes. A syntax error in the live file can split the cluster, so the copy also gives you a place to run diff before anything changes.
cp /etc/pve/corosync.conf /etc/pve/corosync.conf.bak && cp /etc/pve/corosync.conf /etc/pve/corosync.conf.new
Read the current version before editing, because the new file must carry a higher one or corosync rejects it. This is the easiest step to forget.
grep -n config_version /etc/pve/corosync.conf.new
Our cluster was at version 5 by the time of this capture, after the earlier edits and the node removal.
27: config_version: 5
Edit the copy with nano /etc/pve/corosync.conf.new, change the node’s ring0_addr and raise config_version by one. The diff below is from our earlier move of pve-c from 10.0.1.13 to 10.0.1.23, when the three-node cluster was still at version 3, so it shows 3 going to 4 and exactly the two lines that change.
23c23
< ring0_addr: 10.0.1.13
---
> ring0_addr: 10.0.1.23
33c33
< config_version: 3
---
> config_version: 4
Only when the diff shows the lines you meant to change, move the copy into place. A later version-only edit on the same cluster took effect at once, and pvecm status reported Config Version: 6 right after the move.
diff /etc/pve/corosync.conf /etc/pve/corosync.conf.new
mv /etc/pve/corosync.conf.new /etc/pve/corosync.conf
We updated /etc/hosts on every node with sed -i "s/^10.0.1.13 /10.0.1.23 /" /etc/hosts, then changed the interface on the node that moves and applied it. Corosync uses the ring addresses, but the Proxmox services resolve node names through /etc/hosts, so a stale entry breaks migration and the node console even when quorum is fine.
sed -i -e "s#address 10.0.1.13/24#address 10.0.1.23/24#" /etc/network/interfaces && sed -i "s/^10.0.1.13 /10.0.1.23 /" /etc/hosts && grep -A3 "iface vmbr0" /etc/network/interfaces && systemd-run --on-active=2 ifreload -a
On pve-c the bridge came up on the new address, but corosync did not follow on its own. pvecm status on pve-a still listed pve-c under its old IP, and restarting corosync on pve-c alone made it drop out entirely. Restarting corosync on the other two nodes brought all three votes back.
systemctl restart corosync
Then check the membership from any node.
pvecm status | sed -n "/Votequorum/,\$p"
Membership then showed the new address for node 3, and expected and total votes were back at 3, so the cluster was quorate again within seconds of the last restart.
Votequorum information
----------------------
Expected votes: 3
Highest expected: 3
Total votes: 3
Quorum: 2
Flags: Quorate
Membership information
----------------------
Nodeid Votes Name
0x00000001 1 10.0.1.11 (local)
0x00000002 1 10.0.1.12
0x00000003 1 10.0.1.23
We ran this change without disarming HA, and the log on pve-c shows the cost: status change active => lost_agent_lock at 11:15:55, with the lock back at 11:16:25. That is 30 seconds into a roughly 60-second fence window, so a slower restart would have rebooted the node and its VM.
Fix common Proxmox CLI errors
Each error below appeared in our lab while running the commands on this page, and each heading is the exact text the CLI printed. Several of them name the symptom rather than the cause, so the fix is often one step away from where the message points. Search this page for the message first, then read the step it links back to.
cluster not ready – no quorum?
The node cannot see a majority of votes, so /etc/pve is read-only and no guest can start. Check pvecm status for Activity blocked, then fix the network or start corosync on the missing nodes. Use pvecm expected 1 only when the other nodes are confirmed down (Step 8).
VM is locked (backup)
A lock is left behind when a backup, migration or snapshot dies mid-way. qm start 101 refused with this message after a lock was set, and qm unlock 101 cleared it. Make sure no vzdump or migration task is still running first (pgrep -a vzdump), because unlocking a guest under an active task can corrupt the backup. Containers use pct unlock.
snapshot feature is not available
The disk format or storage cannot snapshot, as with raw files on NFS or a directory storage. That also breaks container snapshot backups, so vzdump falls back to suspend mode for the same reason. Move the disk to qcow2 with qm disk move 100 scsi0 shared --format qcow2 --delete 1, or place the VM on ZFS, LVM-thin or Ceph (Step 3).
QEMU guest agent is not running
The VM config has agent: enabled=1 but nothing answers inside the guest. Install and start qemu-guest-agent in the guest, or disable the option with qm set 100 --agent 0. Shutdowns still work without it, because qm shutdown falls back to ACPI after a 3-second agent ping, but agent commands, guest IPs and filesystem freeze during backups fail until it runs (Step 1).
Configuration file ‘nodes/pve-a/qemu-server/100.conf’ does not exist
The VM exists, just not on this node. After the live migration, qm status 100 on pve-a failed with this message. Find the owning node with pvesh get /cluster/resources --type vm and run qm there.
storage ‘shared’ is not online
Proxmox cannot reach the storage backend. Stopping the NFS server produced this on pve-b, where pvesm status marked the storage inactive. Check the server, the export and the network path, and the message clears once the backend answers. A storage disabled with --disable 1 fails differently, with storage 'shared' is disabled.
container did not stop
pct shutdown waits for the container’s init to power off and gives up after the timeout. This one followed an interrupted backup and also blocked a stop-mode vzdump for 10 minutes. pct stop 200 ended it in 2 seconds, and if a lock remains afterwards, pct unlock 200 clears it.
base volume is still in use by linked cloned
A template with linked clones cannot be destroyed, because each clone’s disk reads its unchanged blocks from the template’s base image. Remove the clones or turn them into full clones with qm clone plus --full 1, then destroy the template. qm list does not show the link, so look for base- volumes in each VM’s disk lines to find the dependants.
Commands other Proxmox cheat sheets get wrong
Several Proxmox command lists that rank for these searches print commands with no output, and some of those commands fail or do something else on a real node. We checked the ones that recur against the 9.2 node where we could run them, and against the official 9.2 man pages for the rest. Each bullet says which of the two backs it. None of them is obscure, which is why they keep getting copied from one list to the next.
pvecm expectedas a quorum check: it needs a vote count and changes cluster state. On the lab node, without an argument it failed with400 not enough arguments. To check quorum, runpvecm status.pvenode startup-order: it does not exist. The node printedERROR: unknown command 'pvenode startup-order'and its usage list has no startup subcommand at all. Boot order is per guest, withqm set 100 --startup order=1,up=30.- Passwords in
pveumarguments: the pveum man page does list--passwordonpveum user add(“Initial password”), so the option exists. We did not run it, but any secret passed as an argument ends up in shell history, and on Linux it is readable in the process list while the command runs. On the lab node,pveum user addwithout a password followed bypveum passwd ops@pveprompted twice and echoed asterisks, so nothing reached history. pvesm scanto list volumes: at least one popular list labelspvesm scanas “List volumes”. Ourpvesm scan nfsrun in Step 6 returned the server’s exports, not a storage’s contents. Listing what a storage holds ispvesm list <storage>.- Tools that are not in the CLI reference:
pve-bridge createandpve-create-bondappear in one list, but neither is among the man pages the 9.2 admin guide documents (qm, pct, pvesm, pveam, pvecm, pvesh, pveum, pvenode, vzdump, qmrestore, ha-manager, pvesr, pveceph and the rest). We checked the documentation, not a node. Bridges and bonds are written in/etc/network/interfacesand applied withifreload -a, which is where the lab’svmbr0lives. A file namedpve-bridgedoes exist under/var/lib/qemu-server/, but it is an internal network hook script with nocreatesubcommand.
Most of these come from command lists that were never run, and the password one from a list that ran but did not think about where secrets end up. Before putting any of these into a runbook, run it once on a test node and read what it prints. The official pvecm and qm man pages list every option for the current release. For the KVM layer underneath, the virsh commands cheat sheet covers the libvirt equivalents, and a fresh node is easier to work with after the post-install steps for Proxmox VE.