Episode 3 of the InventiveHQ KVM virtualization series. It reuses the Ubuntu guest created in Episode 2 and prepares it for the storage and networking lessons.
virsh is the command-line control panel for libvirt-managed virtual machines. If you need to find a guest, inspect its disks, change its CPU allocation, open its console, or shut it down without a GUI, this is the tool you use.
This walkthrough uses the guest created in Episode 2 to establish a baseline, change CPU and memory, verify live and persistent state, recover through the console, and review safe deletion.
Verified July 2026 · tested with libvirt 12.0.0, QEMU 10.2.1 and nested KVM on Ubuntu 26.04 LTS
Use the builder to generate exact local, session, or remote commands. It identifies read-only operations, service interruptions, and commands that can delete data before you copy anything to a terminal.
Quick reference: essential virsh commands
# Inventory and inspection
virsh list --all
virsh dominfo demo-vm
virsh domstate demo-vm
virsh domblklist demo-vm
virsh domifaddr demo-vm --source lease
# Lifecycle
virsh start demo-vm
virsh shutdown demo-vm
virsh reboot demo-vm
virsh suspend demo-vm
virsh resume demo-vm
virsh console demo-vm --safe
# Persistent configuration
virsh autostart demo-vm
virsh dumpxml demo-vm --inactive > demo-vm.xml
virsh edit demo-vm
# Resources
virsh setvcpus demo-vm 4 --live --config
virsh setmem demo-vm 4096MiB --live --config
The safe default is to inspect before changing anything:
virsh list --all
virsh dominfo demo-vm
virsh dumpxml demo-vm --inactive > demo-vm.before.xml
That XML export gives you a copy of the persistent definition before a resource or device change. It does not copy the VM's disks or RAM and is not a complete backup.
How KVM, QEMU, libvirt and virsh fit together
These names are frequently treated as synonyms, but each has a separate job:
- KVM is the Linux kernel virtualization capability that uses CPU hardware acceleration.
- QEMU provides the virtual machine process and emulated or paravirtualized devices.
- libvirt manages VM definitions, processes, networks, storage and security policy through a stable API.
- virsh is the command-line client for that libvirt API.
A libvirt-managed VM is called a domain. That is why commands use names such as dominfo, domstate, domifaddr and domblklist.
Use libvirt rather than finding a QEMU PID and changing the process directly. Libvirt understands the persistent definition, applies host security labels, coordinates device operations and handles differences between hypervisor versions.
Choose the correct libvirt connection
Most unexplained virsh problems begin with connecting to the wrong libvirt scope.
System connection
virsh --connect qemu:///system list --all
qemu:///system connects to the system-wide QEMU/libvirt service. It is the normal choice for servers, administrator-managed guests, host bridges, shared storage and VMs that start at boot.
Depending on the distribution, access may come from membership in a libvirt group, polkit authorization, or root privileges. Prefer the distribution's authorization model to running every command through unrestricted sudo.
User session connection
virsh --connect qemu:///session list --all
qemu:///session manages domains owned by the current user. It is useful for desktop or development VMs but has more limited access to system networking and storage.
If virsh list --all returns an empty list while a graphical manager shows running guests, compare these explicitly:
virsh --connect qemu:///system list --all
virsh --connect qemu:///session list --all
They are separate collections. A domain created in one does not automatically appear in the other.
Remote connection over SSH
virsh --connect qemu+ssh://admin@kvm-host.example.com/system list --all
This connects to the remote system libvirt service through SSH. Use SSH keys, confirm the host key, and authorize the remote account for libvirt. The remote host needs the libvirt daemon or modular sockets running, but it does not need a separately exposed TCP management port.
For repeated commands, set the URI for the current shell:
export LIBVIRT_DEFAULT_URI='qemu+ssh://admin@kvm-host.example.com/system'
virsh list --all
virsh dominfo demo-vm
List virtual machines
The unqualified list shows only running domains:
virsh list
Add --all to include shut-off domains:
virsh list --all
Useful filters include:
# Stopped domains only
virsh list --inactive
# Names only, suitable for a shell loop
virsh list --all --name
# Persistent domains configured to start with the host
virsh list --all --persistent --autostart
For read-only automation, request a read-only connection:
virsh --connect qemu:///system --readonly list --all
Inspect VM status and configuration
General domain information
virsh dominfo demo-vm
dominfo reports the state, UUID, CPU count, current and maximum memory, persistence, autostart setting and security model. It is the quickest single check before a change.
For only the current state:
virsh domstate demo-vm
Common results include running, paused and shut off. Lifecycle commands return after sending a request, so scripts should query state rather than assuming the transition has completed.
CPU, memory, disk and network statistics
virsh domstats demo-vm \
--state \
--cpu-total \
--balloon \
--block \
--interface
Unlike the compact dominfo output, domstats returns machine-friendly counters. It can show CPU time, memory balloon information, bytes read and written per disk, network traffic and backing-chain details.
List guest disks
virsh domblklist demo-vm
Example:
Target Source
------------------------------------------------
vda /var/lib/libvirt/images/demo-vm.qcow2
sda /var/lib/libvirt/images/installer.iso
The target is the virtual device name in the domain definition. The source is the host file, block device or managed volume backing it.
Find the VM's IP address
There are three useful sources:
# Libvirt-managed DHCP lease
virsh domifaddr demo-vm --source lease
# QEMU guest agent inside the VM
virsh domifaddr demo-vm --source agent
# Host ARP table
virsh domifaddr demo-vm --source arp
Lease lookup works well for the default libvirt NAT network. It may return nothing for a guest on a host bridge whose address came from an external DHCP server.
Agent lookup can see addresses reported from inside the guest but requires qemu-guest-agent to be installed, running and attached. It also requires a normal writable libvirt connection: libvirt rejects domifaddr --source agent through virsh --readonly.
Start, shut down and reboot VMs
Start an inactive domain
virsh start demo-vm
The domain must have a persistent definition. If you only have an XML definition, import it first with virsh define file.xml.
Request a clean shutdown
virsh shutdown demo-vm
This asks the guest OS to shut down. It is not an immediate power-off and it does not wait for completion.
Check progress:
virsh domstate demo-vm
You can request a specific mechanism:
virsh shutdown demo-vm --mode agent
virsh shutdown demo-vm --mode acpi
Agent mode requires the QEMU guest agent. ACPI requires the guest to respond to a virtual power-button event. With no mode, libvirt selects an available method, which is usually the best default.
Request a clean reboot
virsh reboot demo-vm
As with shutdown, this returns when the request is sent rather than when the VM has finished rebooting. A guest agent can also reject a reboot—for example, when the guest OS has an active shutdown inhibitor—so automation must check the exit status and guest state.
Pause and resume a VM
virsh suspend demo-vm
virsh domstate demo-vm
virsh resume demo-vm
Suspend pauses guest CPU execution but keeps its memory allocated on the host. It is useful for short administrative pauses, not long-term capacity recovery. Use save when you need to release the guest's allocated RAM.
Connect to the serial console
First confirm the domain exposes a console device:
virsh ttyconsole demo-vm
Then connect:
virsh console demo-vm --safe
Press Ctrl+] to disconnect from the console without stopping the VM.
A blank console usually means the guest is not configured to use its serial port. Linux cloud images commonly enable ttyS0; manually installed guests may require a kernel console argument and a serial getty.
--safe refuses to connect if another session already owns the console, preventing two administrators from accidentally sharing the same character stream.
Start a VM automatically with its host
Enable autostart:
virsh autostart demo-vm
Disable it:
virsh autostart demo-vm --disable
Verify the result:
virsh dominfo demo-vm | grep Autostart
Autostart does not guarantee application readiness. A bridged VM can still fail if its host starts the domain before the bridge or remote storage is online.
Export and edit domain XML
Export the persistent definition
virsh dumpxml demo-vm --inactive > demo-vm.xml
--inactive requests the definition that will be used on the next start. Without it, a running guest's XML can include live-only state.
Keep exports out of public repositories until reviewed. Domain XML can contain MAC addresses, host paths, source URLs, device identifiers and, in some configurations, secret references.
Edit with validation
virsh edit demo-vm
This opens the definition in $VISUAL or $EDITOR and validates the XML before replacing the persistent configuration. XML that is structurally valid can still describe unsupported hardware, so export the previous definition first.
To define or redefine a persistent domain from a file:
virsh define demo-vm.xml
define does not start the domain. Run virsh start demo-vm separately.
Change vCPUs and memory
Understand live and persistent scopes
--livechanges the running VM.--configchanges the definition used at its next start.--live --configrequests both.--currentuses the active context chosen by libvirt.
Do not assume a live change persists across reboot.
Change the vCPU count
virsh setvcpus demo-vm 4 --live --config
The live count cannot exceed the maximum vCPU count declared in the domain. The guest OS must also support CPU hotplug for an online increase or decrease.
Libvirt can attach the processors successfully while Linux still leaves them offline. Verify inside the guest:
lscpu | grep -E 'On-line|Off-line'
If the new processors appear in the offline list, review the target CPUs and online them explicitly:
for cpu in /sys/devices/system/cpu/cpu*/online; do
[ "$(cat "$cpu")" = 0 ] && echo 1 | sudo tee "$cpu"
done
nproc
In the verified Ubuntu guest, CPUs 2 and 3 were present but offline after the live hotplug. Onlining them changed nproc from 2 to 4. A clean boot from the persistent four-vCPU definition also brings all four online.
Check the result:
virsh dominfo demo-vm | grep 'CPU(s)'
virsh vcpucount demo-vm
Change current memory
virsh setmem demo-vm 4096MiB --live --config
setmem changes current memory rather than the maximum ceiling. A live reduction or increase normally depends on the virtio balloon device and guest driver. The command can succeed before the guest has finished adjusting, so compare dominfo, dommemstat and monitoring inside the guest.
Changing maximum memory is a separate operation and frequently requires an offline domain:
virsh setmaxmem demo-vm 8192MiB --config
Insert or eject ISO media
Find the virtual CD-ROM target first:
virsh domblklist demo-vm
If the CD-ROM target is sda, eject it from the running and persistent configurations:
virsh change-media demo-vm sda --eject --live --config
Insert an ISO:
virsh change-media demo-vm sda \
/var/lib/libvirt/images/installer.iso \
--insert \
--live \
--config
This changes an existing removable-media device. It does not create a missing CD-ROM controller or device.
Save and restore running state
Save RAM and virtual-device state to a file:
virsh save demo-vm /var/lib/libvirt/backups/demo-vm.state --verbose
When the save completes, the domain is no longer running and its host memory is released.
Restore it:
virsh restore /var/lib/libvirt/backups/demo-vm.state
A save-state file does not contain the VM disks. The disks must still represent the same point in time when restored. Restoring saved RAM against independently changed disks can corrupt the guest, so save/restore is not a substitute for a coordinated backup.
Force operations: destroy and reset
These are emergency controls, not normal shutdown commands.
# Immediate power-off
virsh destroy demo-vm
# Immediate virtual hardware reset
virsh reset demo-vm
destroy stops the running instance but preserves the persistent definition and disk files. reset keeps the domain running while resetting its virtual hardware. Neither gives the guest OS time to flush application or filesystem data.
Use them only when a guest is unresponsive and a clean shutdown or reboot has failed.
Remove a VM definition safely
Preserve disks
virsh shutdown old-vm
virsh undefine old-vm
This removes the persistent libvirt definition but preserves storage. Record the output of domblklist and export the XML before undefining if you may need to reconstruct it.
Delete managed storage too
virsh undefine old-vm --remove-all-storage
This is irreversible. In our disposable validation lab, a cloned VM retained a virtual CD-ROM backed by the same cloud-init ISO as its source. Running --remove-all-storage against the clone deleted both its qcow2 disk and the shared ISO. A storage attachment is not necessarily exclusive to one domain.
Before using the flag:
virsh domblklist old-vm --details
virsh dumpxml old-vm --inactive > old-vm.before-delete.xml
Check every source path and confirm that no other domain uses it:
virsh list --all --name | while read -r vm; do
[ -n "$vm" ] && virsh domblklist "$vm"
done
Troubleshooting common virsh errors
The VM list is unexpectedly empty
Compare system and session connections:
virsh -c qemu:///system list --all
virsh -c qemu:///session list --all
Permission denied connecting to libvirt
Check the URI and your distribution's libvirt authorization. On many systems, log out and back in after adding a user to the relevant group so the new membership reaches the shell.
id
virsh uri
Shutdown does nothing
Confirm that the guest agent or ACPI shutdown mechanism works:
virsh shutdown demo-vm --mode agent
virsh shutdown demo-vm --mode acpi
Do not automatically escalate to destroy without checking application and storage consequences.
Guest-agent IP lookup fails through readonly
This fails by design:
virsh --readonly domifaddr demo-vm --source agent
Use a normal authorized connection for the agent query:
virsh domifaddr demo-vm --source agent
A live resource change succeeds but the value looks unchanged
The guest may still be processing CPU hotplug or memory ballooning. Inspect both libvirt and the guest OS, and verify the persistent definition separately:
virsh dominfo demo-vm
virsh dumpxml demo-vm --inactive
A safe operating sequence
For a routine VM change, use this pattern:
# 1. Confirm the target and current state
virsh dominfo demo-vm
virsh domblklist demo-vm
# 2. Save the persistent definition
virsh dumpxml demo-vm --inactive > demo-vm.before.xml
# 3. Make one scoped change
virsh setvcpus demo-vm 4 --live --config
# 4. Verify live and persistent results
virsh dominfo demo-vm
virsh dumpxml demo-vm --inactive > demo-vm.after.xml
The command builder at the top can chain inspection and configuration operations into one reviewable script. It deliberately requires a second confirmation before copying any script that contains destroy, reset or undefine.