libvirt & virsh Basics: Manage KVM VMs by CLI
Control KVM virtual machines from the command line with virsh: list, start, stop, snapshot, edit domain XML, and manage libvirt networks and storage pools.
Once KVM is running, virsh becomes the command-line cockpit for everything libvirt manages: virtual machines (called domains), virtual networks, and storage pools. It is scriptable, fast, and works perfectly over SSH on a headless server โ which is exactly why orchestration layers like OpenStack drive libvirt under the hood rather than clicking through a GUI.
This tutorial covers the practical virsh commands you will use every day: listing and controlling domains, inspecting and editing domain XML, taking snapshots, and managing networks and storage pools. It assumes you already have a working KVM/QEMU host โ if not, start with our guide on installing KVM/QEMU on Ubuntu 24.04.
Prerequisites
You need Ubuntu 24.04 with qemu-kvm, libvirt-daemon-system, and libvirt-clients installed, your user in the libvirt group, and at least one VM defined (the examples use a domain named ubuntu-test). Confirm the daemon is up with systemctl status libvirtd.
Understanding the connection URI
virsh talks to a libvirt daemon over a connection URI. For local system VMs, that is qemu:///system. Set it once per shell so you can omit --connect on every command:
export LIBVIRT_DEFAULT_URI=qemu:///system
virsh uriListing and inspecting domains
List running domains, or all defined domains including stopped ones:
virsh list
virsh list --allGet detailed information about a single domain โ state, vCPUs, memory, and the autostart flag:
virsh dominfo ubuntu-testInspect live resource usage and the virtual hardware attached to a domain:
virsh domstats ubuntu-test
virsh domblklist ubuntu-test
virsh domiflist ubuntu-testControlling VM lifecycle
The core lifecycle verbs are intuitive. shutdown asks the guest OS to power off gracefully via ACPI, while destroy is the equivalent of pulling the power cord โ use it only when a guest is hung:
virsh start ubuntu-test
virsh shutdown ubuntu-test
virsh reboot ubuntu-test
virsh destroy ubuntu-testTo suspend a VM to RAM and resume it later, or to make a domain start automatically with the host:
virsh suspend ubuntu-test
virsh resume ubuntu-test
virsh autostart ubuntu-test
virsh autostart --disable ubuntu-testEditing domain XML
Every domain is defined by an XML document describing its CPU, memory, disks, and NICs. View it, or open it in your editor with on-save validation:
virsh dumpxml ubuntu-test
virsh edit ubuntu-testChanges to a running domain take effect on the next boot. For a quick live adjustment, set the active memory allocation and vCPU count without editing XML:
virsh setmem ubuntu-test 4194304 --live
virsh setvcpus ubuntu-test 4 --liveTo define a brand-new domain from an XML file you have prepared, or to remove a domain definition (its disk is untouched):
virsh define mydomain.xml
virsh undefine ubuntu-testWorking with snapshots
Snapshots capture a point-in-time state you can roll back to โ invaluable before risky upgrades. Create one, list them, and revert:
virsh snapshot-create-as ubuntu-test snap1 'Before kernel upgrade'
virsh snapshot-list ubuntu-test
virsh snapshot-revert ubuntu-test snap1
virsh snapshot-delete ubuntu-test snap1Note that internal snapshots work with qcow2 disks. For raw or RBD-backed disks the workflow differs, which is one reason production clouds favour storage-layer snapshots.
Managing virtual networks
libvirt ships a default NAT network. List networks, inspect one, and control its lifecycle:
virsh net-list --all
virsh net-dumpxml default
virsh net-start default
virsh net-autostart defaultTo see which IP addresses libvirt's DHCP has handed out on a network:
virsh net-dhcp-leases defaultManaging storage pools and volumes
Storage pools are directories or devices libvirt allocates disk images from. List pools, inspect the default, and create a volume:
virsh pool-list --all
virsh pool-info default
virsh vol-create-as default data.qcow2 10G --format qcow2
virsh vol-list defaultAttach that volume to a running VM as a new virtual disk, persisting the change across reboots:
virsh attach-disk ubuntu-test \
/var/lib/libvirt/images/data.qcow2 vdb \
--persistent --subdriver qcow2Connecting to the console
To reach a VM's serial console directly from the terminal (handy over SSH when there is no network access to the guest yet):
virsh console ubuntu-testPress Ctrl+] to exit the console. If nothing appears, the guest may need a serial console enabled on its kernel command line (console=ttyS0).
Troubleshooting and pitfalls
- โfailed to connect to the hypervisorโ โ you are targeting the wrong URI; export
LIBVIRT_DEFAULT_URI=qemu:///systemor your VMs will appear under your unprivileged user session. - Empty domain list โ almost always the session-vs-system URI confusion above; the VMs exist, you are just looking at the wrong scope.
- XML edits silently reverted โ you edited a running domain; changes apply on next full shutdown and start, not a reboot from inside the guest.
- Snapshot errors on raw disks โ internal snapshots require qcow2; convert the disk or use external snapshots.
Where to go next
You now control KVM entirely from the CLI โ the exact layer OpenStack automates at scale. To give your domains production-grade, replicated storage, continue with using Ceph RBD as KVM/libvirt storage.
Conclusion
virsh turns a KVM host into a fully manageable, scriptable virtualization platform without ever opening a GUI. Mastering domain lifecycle, XML editing, snapshots, networks, and pools is the same mental model clouditiv operators use โ only we drive thousands of these domains through OpenStack so you do not have to. If hand-managing libvirt across a fleet sounds like a lot, clouditiv runs a sovereign, ISO 27001 / BSI C5 private cloud on Ubuntu 24.04 + OpenStack 2025.2 with data in Germany. See our on-premise cloud solution for the managed path.