Context: When a Production VM Goes Down at 2 AM
2:17 AM. Phone buzzing — a client reports the web app isn’t responding. SSH into the hypervisor host, no GUI, no web panel, just a blank terminal. That’s when I realized: knowing virsh is the difference between fixing the issue and going back to sleep versus sitting there until sunrise, fumbling around in the dark.
I run a homelab with Proxmox VE managing 12 VMs and containers — a playground to test everything before pushing to production. Proxmox has a convenient web UI, but underneath it still uses libvirt and QEMU. On the bare-metal server I rent at a data center, the only thing I have is SSH and virsh. This article documents what actually works in production — not textbook theory.
Quick architecture overview: libvirt is an abstraction layer on top of KVM/QEMU — instead of calling QEMU directly with a page-long list of hard-to-remember parameters, you use the libvirt API to keep things clean. virsh (virtualization shell) is libvirt’s CLI client — what I was typing into that terminal at 2 AM. Why bother learning virsh instead of just relying on a GUI?
- Remote servers don’t have a desktop environment
- Script automation: snapshot before every deployment, scheduled backups
- Debug a frozen VM when the management tool’s web UI is also unreachable
- Managing 10+ VMs at once: one loop command gets it all done, no clicking through each one
Installing libvirt and Related Tools
Before installing, check whether your CPU supports virtualization:
grep -E '(vmx|svm)' /proc/cpuinfo | head -1
# vmx = Intel VT-x, svm = AMD-V
# If output is empty → VT is disabled in BIOS, enable it there first
Install on Ubuntu/Debian:
sudo apt update
sudo apt install -y qemu-kvm libvirt-daemon-system libvirt-clients \
bridge-utils virtinst virt-top cpu-checker
# Check if KVM loads correctly
sudo kvm-ok
# Expected: INFO: /dev/kvm exists — KVM acceleration can be used
On CentOS/RHEL/AlmaLinux:
sudo dnf install -y qemu-kvm libvirt libvirt-client virt-install virt-top
sudo systemctl enable --now libvirtd
Add your user to the libvirt group to use virsh without sudo:
sudo usermod -aG libvirt,kvm $USER
newgrp libvirt # Apply immediately without logging out
From Creating a VM to Daily Operations
Creating Your First VM with virt-install
The quickest way to create a VM from an ISO:
sudo virt-install \
--name ubuntu-server-01 \
--ram 2048 \
--vcpus 2 \
--disk path=/var/lib/libvirt/images/ubuntu-server-01.qcow2,size=20,format=qcow2 \
--os-variant ubuntu22.04 \
--network bridge=virbr0 \
--graphics none \
--console pty,target_type=serial \
--location 'http://archive.ubuntu.com/ubuntu/dists/jammy/main/installer-amd64/' \
--extra-args 'console=ttyS0,115200n8 serial'
# List valid os-variant values:
osinfo-query os | grep ubuntu
Or if you already have a cloud image — about 15 minutes faster than installing from ISO, and my preferred approach:
# Download Ubuntu 22.04 cloud image
wget -O /var/lib/libvirt/images/ubuntu-22.04-base.qcow2 \
https://cloud-images.ubuntu.com/jammy/current/jammy-server-cloudimg-amd64.img
# Create a VM from the base image (copy-on-write, saves disk space)
sudo qemu-img create -f qcow2 -b /var/lib/libvirt/images/ubuntu-22.04-base.qcow2 \
-F qcow2 /var/lib/libvirt/images/vm-test.qcow2 20G
Everyday virsh Commands
Grouped by category for easier recall:
Checking VM status:
virsh list --all # List all VMs (running + stopped)
virsh dominfo ubuntu-server-01 # Detailed info for a single VM
virsh domstate ubuntu-server-01 # Check state only
virsh vcpuinfo ubuntu-server-01 # CPU affinity and usage
virsh dommemstat ubuntu-server-01 # Real-time memory stats
Start/Stop VMs:
virsh start ubuntu-server-01 # Power on a VM
virsh shutdown ubuntu-server-01 # Graceful shutdown (sends ACPI signal)
virsh destroy ubuntu-server-01 # Force off — use when VM is frozen and unresponsive
virsh reboot ubuntu-server-01 # Graceful reboot
virsh reset ubuntu-server-01 # Hard reset (equivalent to pressing the reset button)
The commands I used that night at 2 AM:
# VM was completely unresponsive — try graceful first
virsh shutdown production-web-01
# Wait 30s, if it doesn't shut down:
virsh destroy production-web-01
# Bring it back up
virsh start production-web-01
# Monitor the console to watch the boot log
virsh console production-web-01
# Exit console: Ctrl+]
Snapshots — The Feature That Has Saved Me the Most
Before every deployment or system update, I always take a snapshot. It takes 10 seconds but can save an hour of manual rollback if something goes wrong:
# Create a snapshot (VM can be running or stopped)
virsh snapshot-create-as ubuntu-server-01 \
--name "before-nginx-upgrade" \
--description "Snapshot before upgrading nginx to 1.24" \
--atomic
# List snapshots
virsh snapshot-list ubuntu-server-01
# View detailed snapshot info
virsh snapshot-info ubuntu-server-01 before-nginx-upgrade
# Roll back to a previous snapshot (when an upgrade fails)
virsh snapshot-revert ubuntu-server-01 before-nginx-upgrade
# Delete a snapshot you no longer need
virsh snapshot-delete ubuntu-server-01 before-nginx-upgrade
Cloning VMs
Cloning is about 20 minutes faster than a fresh install — handy when you need to spin up a test VM quickly or duplicate a staging environment:
# Shut down the source VM before cloning
virsh shutdown ubuntu-server-01
# Clone
sudo virt-clone \
--original ubuntu-server-01 \
--name ubuntu-server-02 \
--auto-clone
# Clone with a custom disk path
sudo virt-clone \
--original ubuntu-server-01 \
--name ubuntu-server-02 \
--file /var/lib/libvirt/images/ubuntu-server-02.qcow2
Managing Storage Pools
By default, libvirt stores disk images in /var/lib/libvirt/images/. When you add a dedicated SSD or mount a NAS, create a storage pool pointing to it to take advantage of the speed:
# List existing pools
virsh pool-list --all
# Create a new pool pointing to a custom directory
virsh pool-define-as ssd-pool dir --target /mnt/ssd/libvirt/images
virsh pool-build ssd-pool
virsh pool-start ssd-pool
virsh pool-autostart ssd-pool
# List volumes in a pool
virsh vol-list default
# Get the actual path of a disk image
virsh vol-path --pool default ubuntu-server-01.qcow2
# Resize a VM's disk (VM must be shut down)
virsh vol-resize --pool default ubuntu-server-01.qcow2 30G
Editing VM Configuration
# Open the XML config for editing (uses the default EDITOR)
virsh edit ubuntu-server-01
# Change RAM without shutting down the VM (if balloon driver supports it)
virsh setmem ubuntu-server-01 4096M --live --config
# Add vCPUs
virsh setvcpus ubuntu-server-01 4 --live --config
# Autostart when the host reboots
virsh autostart ubuntu-server-01
virsh autostart --disable ubuntu-server-01 # Disable autostart
Monitoring and Inspection
Real-Time Resource Usage
virt-top is the first command I run when I suspect a VM is consuming too much CPU or RAM:
# Top-like interface for VMs — extremely useful
virt-top
# Stats for all running VMs
virsh domstats --live
# CPU stats for a specific VM
virsh cpu-stats ubuntu-server-01 --total
# Disk I/O stats
virsh domblkstat ubuntu-server-01 vda
# Network stats
virsh domifstat ubuntu-server-01 vnet0
Checking Logs When a VM Has Issues
# libvirtd daemon logs
sudo journalctl -u libvirtd -f
# QEMU log for each VM (very detailed, use for debugging)
sudo cat /var/log/libvirt/qemu/ubuntu-server-01.log | tail -50
# Inspect the current VM XML config (includes runtime info)
virsh dumpxml ubuntu-server-01
# Check if the libvirt connection is working
virsh -c qemu:///system version
A Quick Backup Script for All VMs
This script has saved me at least 3 times — most recently when a kernel update left one VM unable to boot. I run it every night before trying any new configuration changes:
#!/bin/bash
# snapshot-all.sh — Snapshot all running VMs
SNAP_NAME="daily-$(date +%Y%m%d)"
for vm in $(virsh list --name --state-running); do
echo "Snapshotting $vm..."
virsh snapshot-create-as "$vm" \
--name "$SNAP_NAME" \
--description "Auto daily snapshot" \
--atomic && echo "OK: $vm" || echo "FAILED: $vm"
done
echo "Done. Snapshots:"
virsh snapshot-list --all 2>/dev/null || true
Common Errors You’ll Encounter
These three errors show up most often during initial setup — and tend to reappear after system updates:
Error: error: Failed to connect socket to '/var/run/libvirt/libvirt-sock'
sudo systemctl start libvirtd
# Or check the socket:
ls -la /var/run/libvirt/
Error: error: Cannot access storage file when starting a VM
# Check file permissions
ls -la /var/lib/libvirt/images/
# Fix permissions
sudo chown libvirt-qemu:kvm /var/lib/libvirt/images/*.qcow2
sudo chmod 660 /var/lib/libvirt/images/*.qcow2
VM won’t shut down (shutdown timeout):
# Increase the shutdown timeout in /etc/libvirt/qemu.conf:
# shutdown_timeout = 60 (seconds)
# Then restart libvirtd
sudo systemctl restart libvirtd
After that 2 AM incident, I wrote all of these commands into a virsh-cheatsheet.md file and left it on the server. Not because I can’t remember them — but because when you’re stressed, glancing at a cheatsheet is a lot faster than Googling.

