cheat-sheets

libvirt and KVM

KVM is the Linux kernel hypervisor and QEMU the emulator that runs the guests. libvirt is the management layer on top: virsh is its shell, virt-install creates guests, qemu-img manages disk images and virt-manager is the desktop GUI. Use it for VMs on a single Linux host without Proxmox (Proxmox VE Overview and Install).

Documentation: libvirt · virt-manager · man virsh, man virt-install, man qemu-img

Verification: virsh, virt-install, virt-clone, virt-xml, virt-manager, virt-viewer and qemu-img options were checked against the man pages shipped with Debian 12 (libvirt 9.0.0, virtinst 4.1.0, QEMU 7.2). No command was run against a live hypervisor. Install package names, default paths and the default network XML path are from general knowledge (unverified). Deleting domains, volumes and snapshots is destructive: back up first.

See also: Hypervisors Compared · VirtualBox · Storage and Filesystems · LVM · ZFS · Netplan · Hardware Inspection · Linux


Concepts #

TERMMEANING
DomainA guest VM. libvirt calls it a domain, defined by XML
Persistent / transientPersistent domains survive reboot (define); transient ones vanish when stopped (create)
NetworkVirtual network on the host. The usual default network is NAT with DHCP
Storage poolA place libvirt keeps disk images (directory, LVM, NFS, iSCSI and more)
VolumeOne disk image or block device inside a pool
qemu:///systemSystem-wide connection, run by root. VMs start at boot and are shared by users in the libvirt group
qemu:///sessionPer-user connection. No root needed, but limited networking
Internal snapshotStored inside the qcow2 file. Simple, needs qcow2
External snapshotWrites go to a new overlay file. Needed for --disk-only

Install and Check Host #

COMMANDDESCRIPTION
grep -Ec '(vmx|svm)' /proc/cpuinfoNon-zero means the CPU has hardware virtualisation (it may still be off in the firmware)
lsmod | grep kvmKVM modules loaded (kvm_intel or kvm_amd)
sudo apt install qemu-kvm libvirt-daemon-system libvirt-clients virtinst virt-managerDebian and Ubuntu packages (unverified: package set varies by release)
sudo systemctl enable --now libvirtdStart the daemon. Newer releases may use modular virt*d daemons (unverified)
sudo usermod -aG libvirt $USERLet a user manage system VMs without sudo. Log out and in afterwards
virsh versionLibrary, API and hypervisor versions
virsh -c qemu:///system list --allTest the connection
virsh capabilitiesHost capabilities as XML
virsh nodeinfoHost CPU and memory summary
virsh domcapabilitiesWhat the emulator supports (machine types, firmware, CPU models)
export LIBVIRT_DEFAULT_URI=qemu:///systemMake virsh, virt-install and virt-manager default to the system connection (unverified)

virsh Basics #

Run virsh COMMAND once, or virsh for an interactive shell. Use -c URI to pick the connection. DOMAIN is a name, ID or UUID.

COMMANDDESCRIPTION
virsh listRunning domains
virsh list --allAll domains, including shut off
virsh list --inactiveDomains that are not running
virsh list --nameNames only, handy for scripts
virsh dominfo DOMAINState, memory, vCPUs, autostart
virsh domstate DOMAINCurrent state
virsh dumpxml DOMAINFull XML definition
virsh dumpxml --inactive DOMAINThe persistent definition rather than the live one
virsh edit DOMAINEdit the XML in $EDITOR. Validated on save
virsh define FILE.xmlCreate or update a persistent domain from XML
virsh domstats DOMAINStatistics (CPU, balloon, block, net)
virsh vcpucount DOMAINCurrent and maximum vCPUs
virsh domifaddr DOMAINGuest IP addresses (from the DHCP lease)
virsh domifaddr DOMAIN --source agentAsk the guest agent instead (needs qemu-guest-agent in the guest). Other sources: lease, arp
virsh domiflist DOMAINVirtual NICs and their MAC addresses
virsh domblklist DOMAINDisks attached to the domain
virsh domblkinfo DOMAIN vda --humanCapacity, allocation and physical size of one disk

Power and Lifecycle #

COMMANDDESCRIPTION
virsh start DOMAINBoot a defined domain
virsh start DOMAIN --consoleBoot and attach to the serial console
virsh start DOMAIN --pausedBoot paused
virsh shutdown DOMAINGraceful shutdown (ACPI or guest agent). Not guaranteed to succeed
virsh shutdown DOMAIN --mode agentChoose the method: acpi, agent, initctl, signal, paravirt
virsh reboot DOMAINGraceful reboot
virsh reset DOMAINHard reset, like the reset button
virsh destroy DOMAINForce power off. Does not delete anything despite the name
virsh destroy DOMAIN --gracefulTry a graceful stop first
virsh suspend DOMAIN / virsh resume DOMAINPause and unpause in memory
virsh managedsave DOMAINSave RAM to disk and stop. The next start restores it
virsh autostart DOMAINStart at host boot
virsh autostart --disable DOMAINStop autostarting
virsh console DOMAINSerial console. Leave with Ctrl+]. The guest needs a serial getty
virsh undefine DOMAINRemove the definition only. Disks stay
virsh undefine DOMAIN --remove-all-storageRemove the definition and its disk volumes. Destructive
virsh undefine DOMAIN --nvramAlso remove the UEFI variable store, needed for UEFI guests
virsh undefine DOMAIN --snapshots-metadataAlso remove snapshot metadata

Create Guests with virt-install #

Minimum options are --memory, storage (--disk or --filesystem) and an install method. Most options take opt1=val1,opt2=val2. Run virt-install --disk=? to list the sub-options of any option.

COMMANDDESCRIPTION
virt-install --osinfo listAccepted OS names for --osinfo
osinfo-query osFuller OS list (package libosinfo-bin)
--name NAMEDomain name (-n)
--memory 4096RAM in MiB
--vcpus 4Virtual CPUs
--cpu host-passthroughPass host CPU features through. Other values include host-model and named models
--osinfo ubuntu24.04OS being installed; sets sensible device defaults (--os-variant is the older name)
--disk size=20New 20 GiB disk in the default pool
--disk path=PATH,size=20,format=qcow2,bus=virtioExplicit path, format and bus
--disk pool=POOL,size=20New volume in a named pool
--disk PATHUse an existing image
--cdrom FILE.isoInstall from an ISO (-c)
--location URL_OR_PATHInstall from a distro tree. Allows --extra-args (-l)
--extra-args "console=ttyS0"Kernel arguments with --location
--pxeNetwork boot install
--importSkip install, boot an existing disk image
--install ubuntu24.04Fetch install media for a known OS (needs network)
--unattendedUnattended install via libosinfo. Combine with --install or --cdrom
--cloud-initAttach a cloud-init config. Bare form generates a root password and disables the account login (see the man page)
--network network=default,model=virtioAttach to the default virtual network (-w)
--network bridge=br0Attach to a host bridge
--network noneNo NIC
--graphics noneNo graphics, serial console only
--graphics vncVNC instead of the default SPICE
--boot uefiUEFI firmware
--machine q35Machine type
--tpm emulatorEmulated TPM (needed for Windows 11)
--noautoconsoleDo not open a console, return at once
--autostartAutostart the guest at host boot
--transientTransient domain, with --import or --boot
--norebootDo not reboot after install
--waitWait for the install to finish. Bare form waits forever; a number is minutes
--connect qemu:///systemUse the system daemon
--print-xmlPrint the XML instead of defining the guest. Still creates storage unless combined with --dry-run
--dry-runGo through the steps without creating storage or defining the guest
--check path_in_use=offDisable a validation check
bash
# Ubuntu server from an ISO, 2 vCPUs, 4 GiB RAM, 20 GiB virtio disk, NAT network
sudo virt-install \
  --connect qemu:///system \
  --name web01 \
  --memory 4096 --vcpus 2 \
  --osinfo ubuntu24.04 \
  --disk size=20,format=qcow2,bus=virtio \
  --network network=default,model=virtio \
  --cdrom /var/lib/libvirt/images/ubuntu-24.04-live-server-amd64.iso \
  --graphics vnc

# Headless: import an existing cloud image
sudo virt-install --name cloud01 --memory 2048 --vcpus 2 \
  --osinfo ubuntu24.04 --import \
  --disk path=/var/lib/libvirt/images/cloud01.qcow2 \
  --network network=default --graphics none --noautoconsole

Clone and Edit #

COMMANDDESCRIPTION
virt-clone --original DOMAIN --auto-cloneClone a shut-off domain and its disks with generated names (-o)
virt-clone -o DOMAIN -n NEWNAME -f NEW.qcow2Clone with an explicit name and disk path
virt-clone ... --nonsparseDo not make the copy sparse
virt-clone ... --reflinkUse reflink copies on a filesystem that supports them
virt-clone ... -m MACSet the new MAC address
virt-xml DOMAIN --edit --memory 8192Change an option in the domain XML without opening an editor
virt-xml DOMAIN --edit --vcpus 4 --print-diffShow what would change
virt-xml DOMAIN --edit --boot menu=onEnable the boot menu
virt-xml DOMAIN --add-device --disk size=10Add a device
virt-xml DOMAIN --remove-device --disk 2Remove the second disk
virt-xml DOMAIN --edit --updateAlso apply to the running guest where possible (--update)
virt-xml DOMAIN --edit ... --confirmAsk before saving

Live Changes and Devices #

Use --live for the running guest, --config for the persistent definition, --current for whichever applies now. Combine --live --config to do both.

COMMANDDESCRIPTION
virsh setvcpus DOMAIN 4 --configSet vCPU count in the definition (applies at next boot)
virsh setvcpus DOMAIN 4 --liveHot-plug vCPUs (up to the maximum)
virsh setvcpus DOMAIN 8 --maximum --configRaise the maximum
virsh setmem DOMAIN 2G --liveChange live memory (balloon) up to the maximum. Size takes a unit suffix
virsh attach-disk DOMAIN /path/disk.qcow2 vdb --subdriver qcow2 --persistentAttach a disk as vdb and keep it in the definition
virsh detach-disk DOMAIN vdb --persistentDetach it
virsh attach-interface DOMAIN network default --model virtio --persistentAdd a NIC on the default network
virsh detach-interface DOMAIN network --mac MAC --persistentRemove a NIC
virsh attach-device DOMAIN device.xml --persistentAttach any device from XML
virsh change-media DOMAIN sda --ejectEject a CD (target name from domblklist)
virsh change-media DOMAIN sda /path/new.iso --insertInsert an ISO
virsh blockresize DOMAIN vda 40GGrow a disk while the guest runs. Resize the partition in the guest afterwards
virsh console DOMAINSee Power and Lifecycle

Networks #

COMMANDDESCRIPTION
virsh net-list --allVirtual networks and whether they are active and autostarted
virsh net-info defaultState, autostart and bridge name
virsh net-dumpxml defaultNetwork XML
virsh net-start defaultStart the network
virsh net-autostart defaultStart it at host boot (--disable to undo)
virsh net-define FILE.xmlDefine a network from XML
virsh net-edit NETWORKEdit the definition
virsh net-destroy NETWORKStop the network (existing guests lose connectivity)
virsh net-undefine NETWORKRemove the definition
virsh net-dhcp-leases defaultCurrent DHCP leases (guest IPs)
virsh net-update NETWORK add ip-dhcp-host "<host mac='MAC' name='HOST' ip='192.168.122.50'/>" --live --configAdd a static DHCP reservation. Form is net-update NETWORK COMMAND SECTION XML (the example element syntax is unverified)

A NAT network definition looks like this. The default network normally uses 192.168.122.0/24.

xml
<network>
  <name>labnet</name>
  <forward mode='nat'/>
  <bridge name='virbr10' stp='on' delay='0'/>
  <ip address='192.0.2.1' netmask='255.255.255.0'>
    <dhcp>
      <range start='192.0.2.100' end='192.0.2.200'/>
    </dhcp>
  </ip>
</network>

To put guests on the LAN, create a host bridge (for example with Netplan) and use --network bridge=br0. A bridged guest talks to the LAN directly; the NAT network does not.

Storage Pools and Volumes #

COMMANDDESCRIPTION
virsh pool-list --allPools and their state
virsh pool-info POOLCapacity and allocation (--bytes for exact figures)
virsh pool-dumpxml POOLPool XML
virsh pool-define-as POOL dir --target /srv/vmsDefine a directory pool
virsh pool-build POOLCreate the target directory or format the backing store
virsh pool-start POOLActivate the pool
virsh pool-autostart POOLActivate at host boot
virsh pool-refresh POOLRescan after files change outside libvirt
virsh pool-destroy POOLDeactivate. Does not delete data
virsh pool-undefine POOLRemove the definition
virsh vol-list POOL --detailsVolumes with type, capacity and allocation
virsh vol-create-as POOL NAME 20G --format qcow2New volume
virsh vol-create-as POOL NAME 20G --format qcow2 --backing-vol BASE.qcow2 --backing-vol-format qcow2New volume backed by a base image
virsh vol-info NAME --pool POOLCapacity, allocation, type (--bytes, --physical)
virsh vol-path NAME --pool POOLHost path of the volume
virsh vol-clone NAME NEWNAME --pool POOLCopy a volume
virsh vol-resize NAME 40G --pool POOLGrow a volume. --delta adds, --shrink shrinks. Shrinking destroys data beyond the new size
virsh vol-upload NAME FILE --pool POOLUpload a local file into a volume
virsh vol-delete NAME --pool POOLDelete a volume. Destructive

Other pool types (fs, netfs, logical, disk, iscsi, scsi, zfs and more) take extra --source-* options; see man virsh under pool-create-as. A directory pool on LVM, ZFS or NFS-mounted storage (NFS) is the simplest way to place images on a specific disk.

bash
sudo mkdir -p /srv/vms
virsh pool-define-as vmstore dir --target /srv/vms
virsh pool-build vmstore
virsh pool-start vmstore
virsh pool-autostart vmstore

Snapshots #

Internal snapshots need qcow2 disks. --disk-only makes external snapshots. Snapshot metadata is stored by libvirt, so undefine needs --snapshots-metadata when snapshots exist.

COMMANDDESCRIPTION
virsh snapshot-create-as DOMAIN SNAP "DESCRIPTION"Take a snapshot with a name and description
virsh snapshot-create-as DOMAIN SNAP --disk-only --atomicExternal disk-only snapshot (guest keeps running)
virsh snapshot-create-as DOMAIN SNAP --disk-only --quiesceAsk the guest agent to freeze filesystems first
virsh snapshot-create-as DOMAIN SNAP --print-xmlShow the XML without creating anything
virsh snapshot-list DOMAINList snapshots
virsh snapshot-list DOMAIN --treeShow parent and child relationships
virsh snapshot-info DOMAIN SNAPDetails of one snapshot (--current for the latest)
virsh snapshot-dumpxml DOMAIN SNAPSnapshot XML
virsh snapshot-revert DOMAIN SNAPRoll back. Discards changes since the snapshot
virsh snapshot-revert DOMAIN SNAP --runningRevert and leave the guest running
virsh snapshot-delete DOMAIN SNAPDelete the snapshot. Changes merge into the children
virsh snapshot-delete DOMAIN SNAP --childrenDelete it and all its children
virsh snapshot-delete DOMAIN SNAP --metadataDelete libvirt’s record only, keep the data
virsh blockcommit DOMAIN vda --active --pivot --verboseMerge an external overlay back into its base and pivot to it (see man virsh for the full workflow)

qemu-img #

Never run qemu-img against an image that a running VM is using. Format and size options go in -f, -O and -o. Run qemu-img create -f qcow2 -o help for the options of a format.

COMMANDDESCRIPTION
qemu-img create -f qcow2 DISK.qcow2 20GNew thin-provisioned qcow2 image
qemu-img create -f qcow2 -o preallocation=metadata DISK.qcow2 20GPreallocate metadata for better speed
qemu-img create -f qcow2 -b BASE.qcow2 -F qcow2 OVERLAY.qcow2Overlay image that records changes against a read-only base
qemu-img info DISK.qcow2Format, virtual size, disk usage, backing file, snapshots
qemu-img info --backing-chain DISK.qcow2Show the whole backing chain
qemu-img info --output=json DISK.qcow2Machine-readable output
qemu-img check DISK.qcow2Check for corruption (-r leaks or -r all to repair)
qemu-img convert -p -f raw -O qcow2 IN.img OUT.qcow2Convert format with progress
qemu-img convert -O qcow2 -c IN.qcow2 OUT.qcow2Compress while converting
qemu-img convert -f vmdk -O qcow2 IN.vmdk OUT.qcow2Convert a VMware or VirtualBox disk
qemu-img convert -f vdi -O qcow2 IN.vdi OUT.qcow2Convert a VirtualBox VDI
qemu-img convert -O raw IN.qcow2 OUT.imgConvert to raw
qemu-img resize DISK.qcow2 +10GGrow by 10 GiB. Resize the partition and filesystem in the guest afterwards
qemu-img resize --shrink DISK.qcow2 10GShrink. Destructive if the data does not fit
qemu-img rebase -b NEWBASE.qcow2 -F qcow2 OVERLAY.qcow2Change the backing file (-u to only change the pointer)
qemu-img commit DISK.qcow2Merge an overlay into its backing file
qemu-img snapshot -l DISK.qcow2List internal snapshots
qemu-img snapshot -c NAME DISK.qcow2Create an internal snapshot
qemu-img snapshot -a NAME DISK.qcow2Apply (revert to) a snapshot
qemu-img snapshot -d NAME DISK.qcow2Delete a snapshot
qemu-img compare A.qcow2 B.qcow2Compare the content of two images
qemu-img measure -O qcow2 DISK.imgRequired size if converted to another format
qemu-img map DISK.qcow2Show which ranges are allocated
qemu-img bench DISK.imgSimple I/O benchmark (-w makes it a write test)

virt-manager and virt-viewer #

virt-manager is the GTK desktop app. It connects to local or remote libvirt (File, Add Connection), creates guests with a wizard, and edits hardware under Show virtual hardware details.

COMMANDDESCRIPTION
virt-managerOpen the manager
virt-manager -c qemu:///systemOpen with a given connection
virt-manager --show-domain-console DOMAINOpen a guest console directly
virt-manager --show-domain-editor DOMAINOpen the hardware editor
virt-manager --show-domain-performance DOMAINOpen the performance view
virt-manager --show-host-summaryOpen host details (also holds Virtual Networks and Storage tabs)
virt-manager --no-forkStay in the foreground
virt-viewer -c qemu:///system DOMAINOpen a lightweight console window for one guest
virt-viewer -c qemu:///system -w -r DOMAINWait for the guest to start and reconnect if it restarts
virt-viewer -c qemu+ssh://USER@HOST/system DOMAINView a guest on a remote host over SSH

Remote management uses connection URIs, for example virsh -c qemu+ssh://USER@HOST/system list --all. SSH keys are covered in OpenSSH Cheat-Sheet.

Troubleshooting #

SYMPTOMFIX
failed to connect to the hypervisor or permission deniedUse -c qemu:///system or sudo, check libvirtd is running, and add your user to the libvirt group
virsh list shows nothing but VMs existYou are on qemu:///session. Use -c qemu:///system
Network default not activevirsh net-start default then virsh net-autostart default
Guest has no IPvirsh net-dhcp-leases default, then virsh domifaddr DOMAIN --source agent. Check the guest requests DHCP
virsh console is blankThe guest needs console=ttyS0 and a serial getty enabled
Cannot undefine a UEFI guestAdd --nvram
Cannot undefine, has snapshotsAdd --snapshots-metadata
Disk image permission deniedImages should sit in a pool the daemon can read (the default is /var/lib/libvirt/images). AppArmor or SELinux may block other paths (SELinux and AppArmor)
Snapshot fails on a raw diskInternal snapshots need qcow2. Use --disk-only for external snapshots
Guest slowUse --cpu host-passthrough, virtio disk and NIC, and check KVM is in use with virsh dumpxml DOMAIN
Logs/var/log/libvirt/qemu/DOMAIN.log and journalctl -u libvirtd (paths unverified)

See Also #

Search all sheets

Tip: / or Ctrl/Cmd+K to open, Esc to close.