How to Use libvirt to Manage KVM Virtual Machines from the Command Line (virsh)

Virtualization tutorial - IT technology blog
Virtualization tutorial - IT technology blog

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.

Share: