article

KVM and Virsh on Linux: Complete Guide to Virtual Machines [2026]

Complete guide to KVM virtualization on Linux: install, configure, and manage VMs with virsh and virt-install. Covers networking, storage pools, cloud-init, and comparison with Vagrant, VirtualBox, and Proxmox.

KVM (Kernel-based Virtual Machine) is a type-1 hypervisor built directly into the Linux kernel since version 2.6.20 (2007). It turns the Linux kernel into a bare-metal hypervisor — no host OS overhead, no extra abstraction layer — just your hardware, the kernel, and virtual machines running at near-native performance.

On top of KVM sits libvirt, a management daemon that provides a stable API and CLI tooling. Virsh is the command-line interface to libvirt, and it’s all you need to create, manage, and destroy VMs from the terminal.

The stack looks like this:

┌──────────────────────┐
│   virsh / virt-install │  ← CLI tools
├──────────────────────┤
│       libvirtd         │  ← management daemon
├──────────────────────┤
│   QEMU / KVM (kernel)  │  ← hypervisor (Linux kernel)
├──────────────────────┤
│      Hardware          │  ← CPU with Intel VT-x / AMD-V
└──────────────────────┘

Why does this matter in 2026? Because every major cloud and virtualization platform — AWS Nitro, Google Compute Engine, OpenStack, Proxmox, oVirt — runs on KVM underneath. Learning KVM directly is learning the foundation that everything else is built on.

Installation

On Debian 12 / Ubuntu 24.04 LTS:

# Install KVM, libvirt, and tools
sudo apt update
sudo apt install -y qemu-kvm libvirt-daemon-system libvirt-clients bridge-utils virtinst virt-manager

# Add your user to the libvirt group
sudo usermod -aG libvirt $(whoami)

# Log out and back in, or run:
newgrp libvirt

# Verify installation
virsh list --all

On Fedora / RHEL 9:

sudo dnf install -y @virtualization
sudo systemctl enable --now libvirtd
sudo usermod -aG libvirt $(whoami)

Verify KVM is working:

# Should show "KVM acceleration can be used"
virt-host-validate qemu

# Or check the kernel module
lsmod | grep kvm

Basic VM Lifecycle with Virsh

Once libvirtd is running, you can manage VMs entirely from the terminal.

Listing VMs

# All VMs (running + stopped)
virsh list --all

# Only running
virsh list

Starting and Stopping

# Start a VM
virsh start my-vm

# Graceful shutdown (ACPI)
virsh shutdown my-vm

# Force power-off
virsh destroy my-vm

# Reboot
virsh reboot my-vm

Creating and Deleting VMs

# Define a VM from an XML config
virsh define /path/to/vm.xml

# Undefine (delete) a VM
virsh undefine my-vm

# Undefine + remove storage volumes
virsh undefine my-vm --remove-all-storage

Connecting to a VM’s Console

# Serial console (like a physical monitor)
virsh console my-vm

# If the VM doesn't have a serial console configured,
# use SSH after the VM boots:
virsh domifaddr my-vm   # get the IP address

Networking: NAT vs Bridged

Default NAT Network

Libvirt creates a default NAT network (192.168.122.0/24) during installation. VMs connected to it get internet access through the host’s connection but are isolated from the rest of your LAN.

# List networks
virsh net-list --all

# View default NAT network info
virsh net-info default
virsh net-dhcp-leases default

Bridged Networking

For VMs that need to appear as regular devices on your physical network (homelab servers, services that need real IPs), create a bridge:

# Create a permanent bridge with netplan (Ubuntu/Debian)
sudo tee /etc/netplan/01-bridge.yaml > /dev/null <<EOF
network:
  version: 2
  ethernets:
    eno1:
      dhcp4: no
  bridges:
    br0:
      interfaces: [eno1]
      dhcp4: yes
EOF

sudo netplan apply

Then create a bridged network in libvirt:

# Create bridge network XML
cat > /tmp/bridge-net.xml << 'NETXML'
<network>
  <name>bridged</name>
  <forward mode="bridge"/>
  <bridge name="br0"/>
</network>
NETXML

virsh net-define /tmp/bridge-net.xml
virsh net-start bridged
virsh net-autostart bridged

Now any VM attached to the bridged network gets an IP from your router’s DHCP.

Storage: Pools and Volumes

Libvirt organizes storage in pools (directories, LVM volume groups, iSCSI targets) and volumes (disk images).

Directory-Based Storage (Default)

# Default pool location: /var/lib/libvirt/images/
virsh pool-list --all

# Create a custom pool
virsh pool-define-as --name vms --type dir --target /data/vms
virsh pool-build vms
virsh pool-start vms
virsh pool-autostart vms

Creating Disk Images

# qcow2 (copy-on-write, thin-provisioned) — recommended
virsh vol-create-as vms my-vm.qcow2 50G --format qcow2

# Raw (pre-allocated, faster I/O)
virsh vol-create-as vms my-vm.raw 50G --format raw

# List volumes in a pool
virsh vol-list vms

Attaching Disks to Running VMs

virsh attach-disk my-vm /data/vms/extra-disk.qcow2 vdc --live --cache writeback

Creating VMs: virt-install

The most practical way to create VMs from the CLI is virt-install:

Minimal Ubuntu Server Example

virt-install \
  --name ubuntu-server \
  --ram 2048 \
  --vcpus 2 \
  --disk path=/data/vms/ubuntu-server.qcow2,size=20,format=qcow2 \
  --os-variant ubuntu24.04 \
  --network network=default \
  --graphics none \
  --console pty,target_type=serial \
  --location https://releases.ubuntu.com/24.04/ubuntu-24.04-live-server-amd64.iso \
  --extra-args "console=ttyS0,115200n8 serial"

This creates a headless Ubuntu VM with serial console access and a 20 GB qcow2 disk attached to the default NAT network.

Debian with Preseed (Fully Automated)

virt-install \
  --name debian-server \
  --ram 2048 \
  --vcpus 2 \
  --disk path=/data/vms/debian-server.qcow2,size=15,format=qcow2 \
  --os-variant debian12 \
  --network network=default \
  --graphics none \
  --console pty,target_type=serial \
  --location https://deb.debian.org/debian/dists/stable/main/installer-amd64/ \
  --initrd-inject /path/to/preseed.cfg \
  --extra-args "auto console=ttyS0,115200n8 serial"

Cloud-Init with Ubuntu Cloud Images

# Download the cloud image
wget https://cloud-images.ubuntu.com/noble/current/noble-server-cloudimg-amd64.img
qemu-img resize noble-server-cloudimg-amd64.img 20G

# Create cloud-init ISO
cat > /tmp/meta-data << 'EOF'
instance-id: kvm-vm-01
local-hostname: kvm-vm-01
EOF

cat > /tmp/user-data << 'EOF'
#cloud-config
ssh_authorized_keys:
  - ssh-ed25519 AAAAC3... your-public-key-here
packages:
  - qemu-guest-agent
runcmd:
  - systemctl enable --now qemu-guest-agent
EOF

mkisofs -o /tmp/cloud-init.iso -V cidata -r /tmp/meta-data /tmp/user-data

# Create the VM
virt-install \
  --name cloud-vm \
  --ram 2048 \
  --vcpus 2 \
  --disk path=noble-server-cloudimg-amd64.img,format=qcow2 \
  --disk path=/tmp/cloud-init.iso,device=cdrom \
  --os-variant ubuntu24.04 \
  --network network=default \
  --graphics none \
  --console pty,target_type=serial \
  --import

Advanced: Snapshots, Migration, and Templates

Snapshots

# Create a snapshot (VM must be running)
virsh snapshot-create-as my-vm pre-upgrade

# List snapshots
virsh snapshot-list my-vm

# Revert to a snapshot
virsh snapshot-revert my-vm pre-upgrade

# Delete a snapshot
virsh snapshot-delete my-vm pre-upgrade

Live Migration (Same Host Storage)

virsh migrate --live my-vm qemu+ssh://target-host/system --verbose

Template from an Existing VM

# Create a generic template VM, then:
virt-sysprep -d template-vm --operations machine-id,logfiles,tmp-files,ssh-hostkeys,net-hostname

# Clone it
virt-clone --original template-vm --name new-vm --file /data/vms/new-vm.qcow2

KVM vs Vagrant vs VirtualBox vs Proxmox

FeatureKVM + VirshVagrantVirtualBoxProxmox
Hypervisor typeType-1 (bare metal)Provisioner (wraps libvirt/VB)Type-2Type-1 (KVM-based)
PerformanceNear-nativeSame as KVM underneath~15-20% overheadNear-native
Headless by defaultYesYesNoYes (web UI)
Learning curveMediumLowLowLow-Medium
Automationvirsh scripts, AnsibleVagrantfile, AnsibleVBoxManageAPI, Terraform
GUIvirt-manager (optional)NoneFull GUIFull web UI
Use caseHomelab, server, CI/CDDev environmentsDesktop devProduction VMs + containers
Disk imagesqcow2, raw, qedBoxes (vagrant format)VDI, VMDK, VHDqcow2, raw, zvol

When to use KVM + virsh directly:

  • You run a headless server or homelab — no GUI, just SSH + terminal.
  • You want to understand virtualization from the ground up — libvirt is the API behind OpenStack, Proxmox, and oVirt.
  • You need to automate VM creation in CI/CDvirt-install is scriptable and idempotent.
  • You’re tired of Vagrant abstractions breaking — the transparency of direct KVM pays off.

When to use Proxmox instead:

  • You need a web UI for multiple hosts — Proxmox’s web GUI is excellent.
  • You want containers (LXC) alongside VMs — Proxmox integrates both.
  • You need clustering and live migration — Proxmox has built-in HA.

The blog already has guides on Proxmox Backup Server setup, running Proxmox on a Mac Mini, and troubleshooting Proxmox login issues. The difference is that those assume you’re using Proxmox’s abstraction layer — this guide teaches you what’s underneath.

Conclusion

KVM is the Linux kernel’s native virtualization engine. Learning to use it directly through virsh and virt-install opens up the entire ecosystem of Linux virtualization — from single-node homelabs to multi-host clusters powered by OpenStack or oVirt.

Start with a single VM. Learn virsh list, virsh start, virsh console. Then move on to networking, storage pools, and cloud-init. By the time you need to scale, you’ll understand exactly what’s happening under the hood — no abstraction layer standing between you and your VMs.

Read also:


You can reach out to discuss this and other topics by email at [email protected]

Subscribe · Free

A monthly letter + a free IP read just for you.

Subscribe and reply to the welcome email with what you're working on. I'll send back a short, honest take on patentability or prior art.

✓ Subscribed · check your inbox
Monthly · no spam · 1-click unsubscribe
1 views
If this was useful
Newsletter · Free
Free IP read when you reply.
A monthly letter from a patent prosecutor and IP consultant at Brazil's Ministry of Health.
✓ SUBSCRIBED · check your inbox
Monthly · no spam · 1-click unsubscribe