Tested on a real install of Proxmox VE 9.2: the installer screen by screen, the 401 repository fix, storage, bridges and VLANs, VMs, containers, snapshots, backups, users and tokens
▶Prefer to watch? This short video runs this page's main commands in a real lab, with on-screen captions and no voiceover. Click to play, or watch it on YouTube.
Proxmox VE: install it, set it up and run it day to day
pveversion; pvesm status; qm list; pct list
Proxmox Virtual Environment (Proxmox VE, "PVE") is a free, open-source virtualization platform: KVM virtual machines and LXC containers, managed from one web page, a command line and a REST API, on a plain Debian base. It is the most common answer to "what do we run instead of VMware?" for small and medium setups, and it is also a fine home-lab hypervisor.
This guide was written against a real install. The lab was Proxmox VE 9.2 (ISO release 1, checksum verified) installed from the ISO into a virtual machine under QEMU/KVM on a laptop: 4 CPUs, 8 GB of RAM, a 60 GB system disk and a second 30 GB disk, with nested virtualization so the lab could start its own guests. Every command output below is real unless it says otherwise. Things the lab could not test (clusters, high availability, Ceph, live migration, passthrough, Proxmox Backup Server) are listed at the end and marked "from Proxmox's documentation, not run here" where they appear.
The companion page, "Moving from VMware to Proxmox VE", covers the migration side: concepts, importing disks and OVF files, and the gotchas.
1. What you get, and what it costs
Virtual machines KVM/QEMU, with UEFI (OVMF) or BIOS (SeaBIOS) firmware, VirtIO devices, snapshots, templates, clones, live disk operations
Containers LXC: a container shares the host's kernel and starts in seconds (section 10)
Management web UI on port 8006, the command line (qm, pct, pvesm, pveum, pvesh), and a REST API
Clustering and HA several nodes managed as one, live migration, high availability (from Proxmox's documentation, not run here)
Storage local disks (LVM-thin, ext4/XFS), ZFS, Btrfs, NFS, iSCSI, Ceph (only the local ones were tested)
Backups built-in vzdump, plus the separate Proxmox Backup Server product
The licence is the GNU Affero GPL v3: the installer shows the EULA and says so. You do not need to pay to use every feature. A paid subscription buys access to the "enterprise" package repository, which gets updates after more testing, plus support. Without one you use the "no-subscription" repository (section 5). The lab confirms what an unsubscribed node says about itself:
$ pvesubscription get
message: There is no subscription key
status: notfound
url: https://www.proxmox.com/en/proxmox-virtual-environment/pricing
2. Before you install: hardware, BIOS, the ISO
CPU virtualization the CPU must support Intel VT-x or AMD-V and it must be switched on in the BIOS/UEFI setup. Check from any Linux:
grep -c -E 'vmx|svm' /proc/cpuinfo a number above 0 means the CPU supports it
RAM 2 GB is Proxmox's stated minimum; plan RAM for every guest on top of that. ZFS wants more (it caches in RAM).
Disks an SSD for the system and the guests; spinning disks are slow for many VMs. If you want ZFS, give it the raw disks, not a hardware RAID volume.
Network a wired NIC with a static address you can reach; the installer asks for the address, gateway and DNS server.
Get the ISO from proxmox.com and check it before you write it anywhere. The download page publishes a SHA-256 for each ISO:
$ sha256sum -c proxmox-ve_9.2-1.iso.sha256
proxmox-ve_9.2-1.iso: OK
The ISO was about 1.6 GB. To make a bootable USB stick on Linux, write the ISO to the whole device (not a partition) and be certain of the device name first, because this erases it (this step is from Proxmox's documentation, not run here, since the lab booted the ISO directly):
lsblk find the stick, for example /dev/sdX
sudo dd if=proxmox-ve_9.2-1.iso of=/dev/sdX bs=1M status=progress conv=fsync
3. The installer, screen by screen
The boot menu has four entries (this is the lab's screen):
Install Proxmox VE (Graphical)
Install Proxmox VE (Terminal UI)
Install Proxmox VE (Terminal UI, Serial Console)
Advanced Options
The graphical and terminal installers ask the same questions. The lab used the terminal one because it can be driven by keyboard alone. In it: Tab moves between buttons and fields, the arrow keys move inside a list, and Enter opens a drop-down or presses a button.
Screen 1, EULA. Read it and choose "I agree".
Screen 2, target disk. It shows the disk the system will go on ("/dev/vda (60.00 GiB)" in the lab). "Advanced options" opens the filesystem choice, and this is the one decision that is hard to change later. The list in version 9.2 is exactly:
ext4 the default: LVM with a thin pool for guests, simple, fast, well understood
XFS the same layout on XFS
ZFS (RAID0) ZFS on the boot disk(s): checksums, snapshots, compression. RAID0 means no redundancy
ZFS (RAID1) mirror
ZFS (RAID10) striped mirrors
ZFS (RAIDZ-1/-2/-3) one, two or three disks of parity
BTRFS (RAID0/1/10) Btrfs, with the same RAID levels
The same screen also asks for the total size, swap size, the maximum root and data volume sizes and the minimum free LVM space. Leave them empty unless you have a reason; the installer picks sensible sizes. The lab kept the default ext4 on its single virtual disk, because a single disk gains nothing from ZFS redundancy. As a rule of thumb (advice, not from the lab): ext4 + LVM-thin for one disk or a hardware RAID card; ZFS RAID1 or RAID10 for two or more disks with no hardware RAID.
Screen 3, location. Country, time zone and keyboard layout. The lab accepted United States, America/Adak (the first entry alphabetically) and U.S. English. On a real server choose your own time zone: it affects log times and backup schedules.
Screen 4, password and email. The root password must be at least 8 characters. The administrator email is where alerts are sent. The installer refuses the default value:
Invalid values: Invalid (default) email address
so type a real address (the lab used admin@example.com).
Screen 5, management network. It shows the NIC (nic0, a virtio network device in the lab) and fills in the address, gateway and DNS server from DHCP. The hostname must be a fully qualified domain name; the lab used pve1.lab.example.com. There is also a checkbox, "Pin network interface names", which keeps the NIC names stable if hardware changes. Use a static address you will keep: changing a node's address later means editing several files (from Proxmox's documentation, not run here).
Screen 6, summary. It lists every choice. The lab's summary read:
Bootdisk filesystem ext4
Bootdisk(s) /dev/vda
Timezone America/Adak
Keyboard layout U.S. English
Administrator email admin@example.com
Management interface nic0
Hostname pve1.lab.example.com
Host IP (CIDR) 10.0.2.15/24
Gateway 10.0.2.2
DNS 10.0.2.3
[X] Automatically reboot after successful installation
Choose Install. The progress bar went from 60 percent (extracting packages) to 91 percent (configuring ifupdown2) to done in about three minutes on the lab's fast virtual disk. Real hardware is usually slower. Remove the USB stick when it reboots, or the machine boots the installer again. That happened in the lab too: the ISO was still attached, so the machine came back to the installer menu until it was started without the ISO.
4. First boot and first login
When the machine boots from its disk the console shows this banner, and a login prompt for the new host name:
Welcome to the Proxmox Virtual Environment. Please use your web browser to
configure this server - connect to:
https://10.0.2.15:8006/
pve1 login:
Open that address (your own IP, and https, port 8006) in a browser. The lab confirmed that the web page answers on that port (an HTTPS request got a normal 200 reply), but it was driven by the command line, so the web page itself was not exercised: the next few details come from Proxmox's documentation, not the lab. The certificate is self-signed, so the browser warns once; accept it, or install your own later. Log in as root with realm "Linux PAM standard authentication". There is also an SSH server on port 22 with the same root password: use SSH keys, not passwords, for anything beyond a lab.
$ pveversion
pve-manager/9.2.2/b9984c6d90a4bd80 (running kernel: 7.0.2-6-pve)
$ pveversion -v | head -5
proxmox-ve: 9.2.0 (running kernel: 7.0.2-6-pve)
pve-manager: 9.2.2 (running version: 9.2.2/b9984c6d90a4bd80)
proxmox-kernel-helper: 9.1.0+fde2
proxmox-kernel-7.0: 7.0.2-6
proxmox-kernel-7.0.2-6-pve-signed: 7.0.2-6
$ grep -E '^(PRETTY_NAME|VERSION_ID)=' /etc/os-release
PRETTY_NAME="Debian GNU/Linux 13 (trixie)"
VERSION_ID="13"
So Proxmox VE 9.2 is Debian 13 with Proxmox's own kernel and packages. Everything you know about Debian (apt, systemd, journalctl, ssh) applies. The logged-in web page may show a pop-up about having no valid subscription: that is only a reminder (from Proxmox's documentation, not run here).
5. Fix the repositories (the 401 errors) and update
A fresh install is set up to use the paid "enterprise" repositories. Without a subscription key apt cannot read them, and the first apt update fails. This is the most common first-day problem, and the lab reproduced it exactly:
$ apt-get update 2>&1 | tail -12
Hit:1 http://deb.debian.org/debian trixie InRelease
Hit:2 http://deb.debian.org/debian trixie-updates InRelease
Hit:3 http://security.debian.org/debian-security trixie-security InRelease
Err:4 https://enterprise.proxmox.com/debian/ceph-squid trixie InRelease
401 Unauthorized [IP: 66.70.154.82 443]
Err:5 https://enterprise.proxmox.com/debian/pve trixie InRelease
401 Unauthorized [IP: 66.70.154.82 443]
Reading package lists...
E: Failed to fetch https://enterprise.proxmox.com/debian/ceph-squid/dists/trixie/InRelease 401 Unauthorized [IP: 66.70.154.82 443]
E: The repository 'https://enterprise.proxmox.com/debian/ceph-squid trixie InRelease' is not signed.
E: Failed to fetch https://enterprise.proxmox.com/debian/pve/dists/trixie/InRelease 401 Unauthorized [IP: 66.70.154.82 443]
E: The repository 'https://enterprise.proxmox.com/debian/pve trixie InRelease' is not signed.
Version 9 keeps repositories in the newer deb822 format, one .sources file each:
$ ls /etc/apt/sources.list.d/
ceph.sources
debian.sources
pve-enterprise.sources
$ cat /etc/apt/sources.list.d/pve-enterprise.sources
Types: deb
URIs: https://enterprise.proxmox.com/debian/pve
Suites: trixie
Components: pve-enterprise
Signed-By: /usr/share/keyrings/proxmox-archive-keyring.gpg
The fix is to switch the two enterprise sources off (add an "Enabled: no" line) and add the no-subscription repository. The lab did exactly this:
$ echo "Enabled: no" >> /etc/apt/sources.list.d/pve-enterprise.sources
$ echo "Enabled: no" >> /etc/apt/sources.list.d/ceph.sources
$ cat /etc/apt/sources.list.d/pve-no-subscription.sources
Types: deb
URIs: http://download.proxmox.com/debian/pve
Suites: trixie
Components: pve-no-subscription
Signed-By: /usr/share/keyrings/proxmox-archive-keyring.gpg
$ apt-get update 2>&1 | tail -8
Hit:1 http://deb.debian.org/debian trixie InRelease
Hit:2 http://security.debian.org/debian-security trixie-security InRelease
Hit:3 http://deb.debian.org/debian trixie-updates InRelease
Get:4 http://download.proxmox.com/debian/pve trixie InRelease [4,357 B]
Get:5 http://download.proxmox.com/debian/pve trixie/pve-no-subscription amd64 Packages [393 kB]
Fetched 398 kB in 1s (271 kB/s)
Reading package lists...
An ISO that is a few months old already has plenty to update:
$ apt list --upgradable 2>/dev/null | wc -l
184
(That count includes the "Listing..." line; the lab had 183 upgradable packages.) Update with apt full-upgrade (or "apt dist-upgrade"), not plain "apt upgrade", because Proxmox updates often add or remove packages, and reboot when the kernel changed. If you use Ceph you also need its repository switched; the lab did not. The no-subscription repository is meant for testing and home labs; production setups should consider the subscription (the choice is yours, and Proxmox documents it).
6. What was installed: storage, network, services, /etc/pve
Disk layout of the lab's 60 GB system disk (ext4 install, so LVM):
$ lsblk -o NAME,SIZE,TYPE,FSTYPE,MOUNTPOINTS
NAME SIZE TYPE FSTYPE MOUNTPOINTS
vda 60G disk
|-vda1 1007K part
|-vda2 512M part vfat
`-vda3 59.5G part LVM2_member
|-pve-swap 7.4G lvm swap [SWAP]
|-pve-root 25G lvm ext4 /
|-pve-data_tmeta 1G lvm
| `-pve-data 17.7G lvm
`-pve-data_tdata 17.7G lvm
`-pve-data 17.7G lvm
vdb 30G disk
vda1 is the BIOS boot area, vda2 the EFI system partition, and vda3 is LVM: swap, the root filesystem and a "thin pool" called data where VM disks live as thin logical volumes. The second disk (vdb) was left untouched for section 7.
$ pvesm status
Name Type Status Total (KiB) Used (KiB) Available (KiB) %
local dir active 25654272 4297080 20028656 16.75%
local-lvm lvmthin active 18575360 0 18575360 0.00%
$ cat /etc/pve/storage.cfg
dir: local
path /var/lib/vz
content iso,vztmpl,backup,import
lvmthin: local-lvm
thinpool data
vgname pve
content rootdir,images
Two storages exist after install. "local" is a plain directory on the root filesystem; it holds ISO images, container templates, backups and import files. "local-lvm" is the thin pool, and holds VM disks (images) and container root filesystems (rootdir). Each storage has a list of content types it accepts; section 15 shows what happens when you ask a storage for something it does not accept.
$ cat /etc/network/interfaces
auto lo
iface lo inet loopback
iface nic0 inet manual
auto vmbr0
iface vmbr0 inet static
address 10.0.2.15/24
gateway 10.0.2.2
bridge-ports nic0
bridge-stp off
bridge-fd 0
The physical NIC (nic0) has no address of its own. The installer created a Linux bridge, vmbr0, plugged the NIC into it and gave the host's address to the bridge. Virtual machines and containers attach to vmbr0 and appear on the LAN as separate hosts, like a virtual switch. This is the equivalent of a vSwitch with an uplink.
The services, and the ports they listen on:
$ systemctl is-active pve-cluster pvedaemon pveproxy pvestatd pve-firewall corosync
active
active
active
active
active
inactive
$ ss -tlnp | awk 'NR==1 || /:(22|8006|3128|85) /{print $1,$4,$6}'
State Local Peer
LISTEN 0.0.0.0:22 users:(("sshd",pid=829,fd=6))
LISTEN 127.0.0.1:85 users:(("pvedaemon
LISTEN *:8006 users:(("pveproxy
LISTEN *:3128 users:(("spiceproxy
LISTEN [::]:22 users:(("sshd",pid=829,fd=7))
pveproxy the web UI and API, port 8006 (HTTPS)
pvedaemon does the work for the API, only on localhost (port 85)
spiceproxy SPICE console proxy, port 3128
pve-cluster mounts the cluster configuration database at /etc/pve
corosync cluster messaging: inactive on a single node, as expected
Corosync being inactive is normal on one machine. Asking for cluster status says so politely:
$ pvecm status
Error: Corosync config '/etc/pve/corosync.conf' does not exist - is this node part of a cluster?
The firewall service runs but is switched off by default:
$ pve-firewall status
Status: disabled/running
/etc/pve is special: it is not a normal directory but a database filesystem shared by every node in a cluster, and every setting lives in it (even on a single node):
$ ls /etc/pve
authkey.pub datacenter.cfg firewall ha local lxc mapping nodes openvz priv
pve-root-ca.pem pve-www.key qemu-server sdn storage.cfg user.cfg virtual-guest vzdump.cron
VM configurations are text files in /etc/pve/qemu-server/ and container ones in /etc/pve/lxc/. The scheduled backup jobs are in vzdump.cron. Do not edit /etc/pve by hand unless you know why; use the web UI or the commands below, which keep the cluster consistent.
7. Storage: adding a ZFS pool and a directory
The lab turned its spare 30 GB disk into a ZFS pool and registered it as storage. The first command makes the pool, the second tells Proxmox to use it for guest disks and container volumes:
$ zpool create -f -o ashift=12 tank /dev/vdb && zpool status tank | head -9
pool: tank
state: ONLINE
config:
NAME STATE READ WRITE CKSUM
tank ONLINE 0 0 0
vdb ONLINE 0 0 0
errors: No known data errors
$ pvesm add zfspool tank-zfs --pool tank --content images,rootdir
$ pvesm status
Name Type Status Total (KiB) Used (KiB) Available (KiB) %
local dir active 25654272 4314548 20011188 16.82%
local-lvm lvmthin active 18575360 66871 18508488 0.36%
tank-zfs zfspool active 29966336 384 29965952 0.00%
ashift=12 means 4 KiB sectors, which suits nearly all current disks. A VM disk on ZFS is a "zvol" (a block device inside the pool):
$ qm set 100 --scsi2 tank-zfs:1
update VM 100: -scsi2 tank-zfs:1
scsi2: successfully created disk 'tank-zfs:vm-100-disk-0,size=1G'
$ zfs list -t all -o name,used,refer,mountpoint | head -8
NAME USED REFER MOUNTPOINT
tank 1.02G 96K /tank
tank/vm-100-disk-0 1.02G 56K -
One thing to notice: the 1 GB disk shows 1.02G "used" straight away, because ZFS reserves the full size of a volume by default. LVM-thin allocates lazily (lvs showed data_percent 0.00 for a fresh 2 GB disk), so the two behave differently when you over-commit.
A directory or NFS share works the same way with a different type (pvesm add dir ... or pvesm add nfs ..., from Proxmox's documentation, not run here). Remember to give each storage only the content types it should hold.
8. Networking: the bridge, VLANs
A VLAN-aware bridge lets every guest choose its own VLAN tag, like a trunk port with port groups. The lab switched vmbr0 on by adding two lines and reloading the network without a reboot:
$ sed -n '/auto vmbr0/,/source/p' /etc/network/interfaces
auto vmbr0
iface vmbr0 inet static
address 10.0.2.15/24
gateway 10.0.2.2
bridge-ports nic0
bridge-stp off
bridge-fd 0
bridge-vlan-aware yes
bridge-vids 2-4094
$ ifreload -a; echo reload-exit=$?; bridge vlan show | head -6
reload-exit=0
port vlan-id
nic0 1 PVID Egress Untagged
2
3
4
5
Then a guest's network card carries the tag:
$ qm set 100 --net0 virtio,bridge=vmbr0,tag=20 && qm config 100 | grep ^net0
update VM 100: -net0 virtio,bridge=vmbr0,tag=20
net0: virtio=BC:24:11:7C:DC:49,bridge=vmbr0,tag=20
Test network changes with a console or out-of-band access available. Reloading a wrong /etc/network/interfaces on a remote host can cut off your own connection. Keep a copy first (the lab did: cp /etc/network/interfaces /root/interfaces.bak).
9. Create a virtual machine from the command line
The web page's "Create VM" wizard does the same thing. The command line is faster to repeat and easy to put in a script. The lab created a small Linux guest with a 2 GB disk:
$ qm create 100 --name lab-vm --memory 512 --cores 1 --cpu x86-64-v2-AES --net0 virtio,bridge=vmbr0 --scsihw virtio-scsi-single --scsi0 local-lvm:2 --ostype l26 --agent enabled=1
Logical volume "vm-100-disk-0" created.
Logical volume pve/vm-100-disk-0 changed.
scsi0: successfully created disk 'local-lvm:vm-100-disk-0,size=2G'
$ qm config 100
agent: enabled=1
boot: order=scsi0;net0
cores: 1
cpu: x86-64-v2-AES
memory: 512
meta: creation-qemu=11.0.0,ctime=1791124690
name: lab-vm
net0: virtio=BC:24:11:57:00:1B,bridge=vmbr0
ostype: l26
scsi0: local-lvm:vm-100-disk-0,size=2G
scsihw: virtio-scsi-single
smbios1: uuid=cae3f5a9-35d8-4f32-abfa-be5e5f462991
vmgenid: cc097dc1-814c-4896-8da6-78c5478fbfc5
What the choices mean:
--scsihw virtio-scsi-single the disk controller. VirtIO SCSI is the fast paravirtual one; "single" gives each disk its own controller, which allows an I/O thread per disk
--scsi0 local-lvm:2 a new 2 GB disk on the thin pool, as the first SCSI disk
--net0 virtio,bridge=vmbr0 a VirtIO network card on the bridge. A MAC address is generated (BC:24:11:... here)
--cpu x86-64-v2-AES a defined CPU model instead of "host"; guests can move between different CPUs. "host" is faster but ties a guest to the CPU type
--ostype l26 tells Proxmox it is a modern Linux guest, which sets sane defaults
--agent enabled=1 expects the QEMU guest agent inside the guest (install the qemu-guest-agent package there); it gives the host the guest's IP and lets it shut the guest down cleanly
boot: order=scsi0;net0 boot from the disk first, then the network
To install an operating system, upload an ISO to the "local" storage (it goes into /var/lib/vz/template/iso) and attach it with --ide2 local:iso/NAME.iso,media=cdrom, then set boot order and start (not run in the lab, which used empty disks to keep downloads small).
$ qm list
VMID NAME STATUS MEM(MB) BOOTDISK(GB) PID
100 lab-vm stopped 512 2.00 0
$ qm start 100; sleep 5; qm status 100
status: running
$ qm stop 100; qm status 100
status: stopped
These guests ran inside the lab, which is itself a virtual machine, so nested virtualization worked: KVM acceleration was available to the guests. On a real server you normally have it without any extra setting. "qm stop" is a hard power-off; "qm shutdown" asks the guest to shut down cleanly (needs the guest agent or ACPI).
10. Containers (LXC)
A container is not a separate machine: it uses the host's kernel, so it starts quickly and uses little memory, but it can only run Linux. Container templates are downloaded with pveam:
$ pveam update
update successful
$ pveam available --section system | head -6
system almalinux-10-default_20250930_amd64.tar.xz
system almalinux-9-default_20240911_amd64.tar.xz
system alpine-3.22-default_20250617_amd64.tar.xz
system alpine-3.23-default_20260116_amd64.tar.xz
system alpine-3.24-default_20260714_amd64.tar.xz
system alpine-3.24-default_20260803_arm64.tar.xz
$ pveam download local alpine-3.24-default_20260714_amd64.tar.xz
... 100% 2.34M=1.4s
calculating checksum...OK, checksum verified
$ pct create 200 local:vztmpl/alpine-3.24-default_20260714_amd64.tar.xz --hostname lab-ct --memory 256 --cores 1 --rootfs local-lvm:1 --net0 name=eth0,bridge=vmbr0,ip=dhcp --unprivileged 1 --features nesting=1
extracting archive '/var/lib/vz/template/cache/alpine-3.24-default_20260714_amd64.tar.xz'
Total bytes read: 11694080 (12MiB, 44MiB/s)
Detected container architecture: amd64
$ pct config 200
arch: amd64
cores: 1
features: nesting=1
hostname: lab-ct
memory: 256
net0: name=eth0,bridge=vmbr0,hwaddr=BC:24:11:60:6B:A6,ip=dhcp,type=veth
ostype: alpine
rootfs: local-lvm:vm-200-disk-0,size=1G
swap: 512
unprivileged: 1
$ pct start 200; sleep 6; pct list
VMID Status Lock Name
200 running lab-ct
$ pct exec 200 -- sh -c 'cat /etc/os-release | head -2; uname -r; hostname'
NAME="Alpine Linux"
ID=alpine
7.0.2-6-pve
lab-ct
...
Look at the kernel line: the container reports 7.0.2-6-pve, which is the host's kernel. That is what "shares the kernel" means, and it is why a container cannot run a different kernel or a Windows guest. "--unprivileged 1" runs the container's root as an ordinary user on the host, which is the safer default. "--features nesting=1" allows running containers inside it (Docker inside LXC, for example).
$ pct snapshot 200 clean && pct listsnapshot 200
freeze guest filesystem
Logical volume "snap_vm-200-disk-0_clean" created.
thaw guest filesystem
`-> clean 2026-10-04 05:40:29 no-description
`-> current You are here!
Use a VM when you need a different kernel or operating system, strong isolation or a Windows guest; use a container for lightweight Linux services.
11. Snapshots, templates and clones
A snapshot freezes a guest's disks and configuration so you can return to it. The lab took one, changed the memory, and rolled back:
$ qm snapshot 100 before-change --description 'clean state'
snapshotting 'drive-scsi0' (local-lvm:vm-100-disk-0)
Logical volume "snap_vm-100-disk-0_before-change" created.
$ qm set 100 --memory 1024
update VM 100: -memory 1024
$ qm listsnapshot 100
`-> before-change 2026-10-04 05:38:15 clean state
`-> current You are here!
$ qm rollback 100 before-change
Logical volume "vm-100-disk-0" successfully removed.
Logical volume "vm-100-disk-0" created.
Logical volume pve/vm-100-disk-0 changed.
$ qm config 100 | grep -E '^(memory|name|scsi0)'
memory: 512
name: lab-vm
scsi0: local-lvm:vm-100-disk-0,size=2G
The memory went back to 512, and the volume was recreated from the snapshot. A snapshot on LVM-thin is a separate thin volume:
$ lvs --noheadings -o lv_name,lv_size,pool_lv,data_percent pve
data 17.71g 0.00
root <25.03g
snap_vm-100-disk-0_before-change 2.00g data
swap <7.38g
vm-100-disk-0 2.00g data 0.00
A snapshot is not a backup: it lives on the same storage as the disk it protects. Keep snapshots short-lived, and use backups (section 12) for real protection.
Turn a prepared VM into a template, then make clones from it. A clone of a template is a "linked clone", sharing the template's data until it diverges, so it is created instantly and uses almost no space:
$ qm template 102
Renamed "vm-102-disk-0" to "base-102-disk-0" in volume group "pve"
Logical volume pve/base-102-disk-0 changed.
$ qm clone 102 104 --name from-template
creating a clone of VM 102 with ID 104
create linked clone of drive scsi0 (local-lvm:base-102-disk-0)
Logical volume "vm-104-disk-0" created.
Logical volume pve/vm-104-disk-0 changed.
$ qm list
VMID NAME STATUS MEM(MB) BOOTDISK(GB) PID
100 lab-vm stopped 512 2.00 0
102 lab-vm stopped 512 2.00 0
104 from-template stopped 512 2.00 0
(Guest 102 was restored from a backup in the next section, and then converted to a template. A template can no longer be started; clone it instead.)
12. Backups and restores
vzdump is the built-in backup tool. "--mode stop" shuts the guest down for a perfectly consistent copy; "snapshot" backs up a running guest without stopping it; "suspend" is in between. The lab backed up the stopped VM to the "local" storage with zstd compression:
$ vzdump 100 --storage local --mode stop --compress zstd
INFO: snapshots found (not included into backup)
INFO: creating vzdump archive '/var/lib/vz/dump/vzdump-qemu-100-2026_10_04-05_39_40.vma.zst'
INFO: starting kvm to execute backup task
INFO: starting backup via QMP command
INFO: started backup task '1ef98f6b-8280-4746-a525-fb908ee33eca'
INFO: 100% (2.0 GiB of 2.0 GiB) in 1s, read: 2.0 GiB/s, write: 0 B/s
INFO: backup is sparse: 2.00 GiB (100%) total zero data
INFO: transferred 2.00 GiB in 1 seconds (2.0 GiB/s)
INFO: stopping kvm after backup task
INFO: archive file size: 73KB
INFO: Finished Backup of VM 100 (00:00:03)
INFO: Backup job finished successfully
INFO: notified via target `mail-to-root`
$ ls -lh /var/lib/vz/dump
-rw-r--r-- 1 root root 1.1K Oct 4 05:39 vzdump-qemu-100-2026_10_04-05_39_40.log
-rw-r--r-- 1 root root 74K Oct 4 05:39 vzdump-qemu-100-2026_10_04-05_39_40.vma.zst
Note the first line: "snapshots found (not included into backup)". A backup contains the current state only, not the guest's snapshots. The disk was empty, so it compressed from 2 GB to 73 KB; yours will be much bigger. Restore into a new guest ID:
$ qmrestore /var/lib/vz/dump/vzdump-qemu-100-*.vma.zst 102 --storage local-lvm
progress 98% (read 2104557568 bytes, duration 0 sec)
progress 99% (read 2126053376 bytes, duration 0 sec)
progress 100% (read 2147483648 bytes, duration 0 sec)
total bytes read 2147483648, sparse bytes 2147483648 (100%)
rescan volumes...
Scheduled backups are set up in the web page (Datacenter, Backup) and stored in /etc/pve/vzdump.cron. A backup on the same machine does not survive that machine dying: send backups to another disk, another host (NFS) or a Proxmox Backup Server (from Proxmox's documentation, not run here: PBS adds deduplication, incremental backups and verification).
13. Users, permissions and API tokens
Only root@pam exists after install:
$ pveum user list
userid email enable realm-type
root@pam admin@example.com 1 pam
(The real output is a wide table; the other columns were empty.) Authentication realms are PAM (Linux users), the Proxmox VE realm (users stored by Proxmox), and LDAP/Active Directory/OpenID (from Proxmox's documentation, not run here). Permissions are given to users or groups with roles on a path, for example the built-in Administrator and PVEAdmin roles:
$ pveum role list | head -8
roleid privs
Administrator Datastore.Allocate,Datastore.AllocateSpace,Datastore.AllocateTemplate,Datastore.Audit,Group.Allocate,...
NoAccess
PVEAdmin Datastore.Allocate,Datastore.AllocateSpace,Datastore.AllocateTemplate,Datastore.Audit,Group.Allocate,...
For scripts and automation use an API token instead of a password. The secret is shown once, when it is created (the lab hid it here):
$ pveum user token add root@pam ci --privsep 0 --output-format json
{"full-tokenid":"root@pam!ci","info":{"privsep":"0"},"value" : "<secret hidden>"}
$ pveum user token list root@pam
tokenid comment expire privsep
ci 0 0
"--privsep 0" gives the token the same rights as its user; a better habit is to leave privilege separation on and give the token only the roles it needs. The REST API is also reachable on the command line with pvesh, which is handy for scripts:
$ pvesh get /version --output-format json-pretty
{
"release" : "9.2",
"repoid" : "b9984c6d90a4bd80",
"version" : "9.2.2"
}
$ pvesh ls /
Dr--- access
Dr--- cluster
Dr--- nodes
Drwcd pools
Dr-c- storage
-r--- version
Turn on two-factor authentication for every administrator and put the web page behind a VPN or firewall rather than open to the internet (advice, not tested here).
14. Command cheat sheet
pveversion -v versions of Proxmox and its main packages
pvesm status storages and their free space
pvesm add ... / pvesm set NAME --content add a storage, or change what it may hold
qm list all virtual machines on this node
qm create / qm config ID / qm set ID ... create, show and change a VM
qm start|stop|shutdown|reset ID power: stop is hard, shutdown is polite
qm snapshot|listsnapshot|rollback ID ... snapshots
qm clone ID NEWID --name NAME clone (linked if the source is a template)
qm template ID turn a VM into a template
qm disk import ID FILE STORAGE bring a disk image in (older name: qm importdisk)
qm importovf ID FILE.ovf STORAGE create a VM from an OVF file
qmrestore FILE ID --storage STORAGE restore a backup to a new ID
vzdump ID --storage S --mode stop|snapshot|suspend --compress zstd back up
pct list|create|start|stop|exec|snapshot containers
pveam update|available|download|list container templates
pveum user|role|token ... users, roles, API tokens
pvesh get|ls /PATH the REST API from the shell
pvecm status cluster status (error on a standalone node: normal)
pve-firewall status firewall service state
ifreload -a apply /etc/network/interfaces without a reboot
15. Gotchas and what the error means
"401 Unauthorized" from enterprise.proxmox.com in apt update
No subscription key. Disable the two enterprise sources and add the no-subscription one (section 5).
"Invalid (default) email address" in the installer
The default admin email is refused: type a real one.
Machine comes back to the installer menu after the install
The ISO or USB stick is still attached; remove it (or boot from the disk).
"storage local does not support vm images" when importing or creating a disk
The storage's content list does not include images. The default "local" directory holds iso, vztmpl, backup and import only. Allow it with:
pvesm set local --content iso,vztmpl,backup,import,images
(the lab re-ran the import after this and it worked; see the migration page).
"Error: Corosync config '/etc/pve/corosync.conf' does not exist" from pvecm status
Normal on a single node; you have no cluster.
A snapshot shows in qm listsnapshot but the backup says "snapshots found (not included into backup)"
Backups never contain snapshots. They hold the current state.
The 1 GB ZFS volume shows 1.02G used immediately
ZFS reserves volume space up front; LVM-thin does not. Plan capacity accordingly.
Cannot start a template
Templates are read-only masters: clone them and start the clone.
Lost your connection after editing /etc/network/interfaces
Always keep a copy and have a console available when you change the management bridge.
16. What this page did not test
Everything above was run. The following exist in Proxmox VE but were not run in the lab, and the page only mentions them: clusters, quorum, corosync links and live migration (needs at least two nodes); high availability and fencing; Ceph and other shared storage; PCI and GPU passthrough; the software-defined network features; the firewall rules; Proxmox Backup Server; the web user interface itself (the lab used the command line and the API); installing a real guest operating system from an ISO; and the graphical installer (the terminal installer asks the same questions). Treat anything about them as pointers to Proxmox's own documentation, which is excellent.
17. Tips
1. Check the ISO's SHA-256 before writing it to a stick.
2. Choose the filesystem in the installer carefully: moving from ext4 to ZFS later means reinstalling.
3. Use a static address and a real FQDN, and write both down.
4. Switch to the no-subscription repository on a lab on day one, then update with apt full-upgrade and reboot for new kernels.
5. Give each storage only the content types it should hold.
6. Use VirtIO disks and network cards and install the QEMU guest agent in every guest.
7. Snapshots are a quick undo, not a backup: schedule real backups to another machine.
8. Use API tokens, not the root password, in scripts, and keep their secrets out of git.
9. Practise a restore before you need one.
10. Keep a console or out-of-band access ready before changing the management network.