KVM (Kernel-based Virtual Machine) is the Linux kernel module that turns a server into a hypervisor using the CPU's hardware virtualization extensions. QEMU emulates the virtual hardware for each guest, and libvirt provides a stable API and tools (virsh, virt-install) to manage them. In this tutorial you will install the KVM stack on Ubuntu 24.04, verify that hardware acceleration works, and create an Ubuntu virtual machine from an official cloud image that you can reach over SSH.

Prerequisites

To follow this tutorial you need:

  • A server running Ubuntu 24.04 LTS with a 64-bit x86 CPU that supports Intel VT-x or AMD-V. A bare metal server, such as a CubePath dedicated server, is the natural choice. A VPS only works if its provider enables nested virtualization.
  • A non-root user with sudo privileges and an SSH key pair on that user (~/.ssh/id_ed25519.pub). Create one with ssh-keygen -t ed25519 if needed.
  • Enough resources for the host plus guests: at least 4 GB of RAM and 30 GB of free disk space for this tutorial.

The same packages and commands apply to Debian 12.

Step 1 - Checking hardware virtualization support

KVM needs the CPU flag vmx (Intel) or svm (AMD). Count the CPU threads that expose it:

grep -Ec '(vmx|svm)' /proc/cpuinfo

Any number greater than 0 means the CPU supports virtualization. If the result is 0 on a physical server, enable Intel VT-x or AMD-V (sometimes called SVM Mode) in the BIOS/UEFI settings. On a VPS it means nested virtualization is not available.

Ubuntu's cpu-checker package confirms that the kernel can actually use it:

sudo apt update
sudo apt install cpu-checker
sudo kvm-ok
INFO: /dev/kvm exists
KVM acceleration can be used

Step 2 - Installing KVM, QEMU and libvirt

Install the hypervisor and management tools:

sudo apt install qemu-system-x86 qemu-utils libvirt-daemon-system libvirt-clients virtinst xorriso

These packages provide:

  • qemu-system-x86: the QEMU emulator with KVM acceleration for x86_64 guests.
  • qemu-utils: qemu-img, used to create and convert disk images.
  • libvirt-daemon-system: the libvirt daemon, its systemd units and the default NAT network.
  • libvirt-clients: virsh and virt-host-validate.
  • virtinst: virt-install and virt-clone to create and copy VMs.
  • xorriso: builds the small cloud-init ISO that virt-install attaches to new VMs.

The libvirt daemon starts automatically. Verify it:

systemctl status libvirtd
● libvirtd.service - libvirt legacy monolithic daemon
     Loaded: loaded (/usr/lib/systemd/system/libvirtd.service; enabled; preset: enabled)
     Active: active (running) since ...

If you want a graphical manager on a desktop machine, also install virt-manager. It is not needed on a headless server.

Step 3 - Granting your user access to libvirt

By default, only root can manage system-wide VMs. Add your user to the libvirt and kvm groups:

sudo usermod -aG libvirt,kvm "$USER"

Log out and back in for the group change to apply. Then tell virsh to use the system instance (qemu:///system) rather than a per-user session, which is where libvirt keeps its networks and storage:

echo 'export LIBVIRT_DEFAULT_URI=qemu:///system' >> ~/.bashrc
source ~/.bashrc

Verify that you can talk to the daemon without sudo:

virsh list --all
 Id   Name   State
--------------------

An empty list is expected at this point.

Step 4 - Validating the host

virt-host-validate checks that the kernel modules, device nodes and cgroups KVM needs are in place:

virt-host-validate qemu
  QEMU: Checking for hardware virtualization                                 : PASS
  QEMU: Checking if device '/dev/kvm' exists                                 : PASS
  QEMU: Checking if device '/dev/kvm' is accessible                          : PASS
  QEMU: Checking if device '/dev/vhost-net' exists                           : PASS
  QEMU: Checking if device '/dev/net/tun' exists                             : PASS
...
  QEMU: Checking for device assignment IOMMU support                         : WARN (No ACPI DMAR table found, IOMMU either disabled in BIOS or not supported by this hardware platform)

All checks should report PASS. The IOMMU warning only matters if you plan to pass PCI devices such as GPUs through to a VM, and can be ignored otherwise.

Also confirm that the KVM module is loaded:

lsmod | grep kvm
kvm_amd               208896  0
kvm                  1409024  1 kvm_amd

On Intel CPUs you will see kvm_intel instead of kvm_amd.

Step 5 - Checking the default network

libvirt creates a NAT network called default on the virbr0 bridge. Guests on it get an address in 192.168.122.0/24 from libvirt's DHCP server and reach the Internet through the host.

virsh net-list --all
 Name      State    Autostart   Persistent
--------------------------------------------
 default   active   yes         yes

If the network is inactive, start it and enable it at boot:

virsh net-start default
virsh net-autostart default

Guests on this network are reachable from the host but not from outside. To give VMs addresses on your public network, you will later need a Linux bridge configured with netplan.

Step 6 - Preparing a cloud image and cloud-init

The fastest way to create a VM is to boot an official Ubuntu cloud image instead of running an installer. Download the Ubuntu 24.04 image into libvirt's image directory:

sudo wget -O /var/lib/libvirt/images/noble-server-cloudimg-amd64.img \
  https://cloud-images.ubuntu.com/noble/current/noble-server-cloudimg-amd64.img

Create a 20 GB disk for the VM that uses the downloaded image as a read-only backing file. The VM only stores its own changes, so the base image can be reused by many VMs:

sudo qemu-img create -f qcow2 -F qcow2 \
  -b /var/lib/libvirt/images/noble-server-cloudimg-amd64.img \
  /var/lib/libvirt/images/vm1.qcow2 20G

Cloud images have no password and expect cloud-init to configure them on first boot. Create a user-data file that sets the hostname and installs your public SSH key for the default ubuntu user:

nano ~/vm1-user-data.yaml
#cloud-config
hostname: vm1
ssh_authorized_keys:
  - ssh-ed25519 AAAA...your_public_key... your_user@host
package_update: true
packages:
  - qemu-guest-agent
runcmd:
  - systemctl enable --now qemu-guest-agent

Replace the key line with the contents of your ~/.ssh/id_ed25519.pub. The QEMU guest agent lets the host query the VM's IP addresses and shut it down cleanly.

Step 7 - Creating the virtual machine

Create and boot the VM with virt-install. The --import flag boots the existing disk instead of running an installer, and --cloud-init attaches your user-data:

virt-install \
  --name vm1 \
  --memory 2048 \
  --vcpus 2 \
  --cpu host-passthrough \
  --osinfo linux2022 \
  --disk path=/var/lib/libvirt/images/vm1.qcow2,format=qcow2,bus=virtio \
  --network network=default,model=virtio \
  --cloud-init user-data="$HOME/vm1-user-data.yaml" \
  --channel unix,target.type=virtio,target.name=org.qemu.guest_agent.0 \
  --graphics none \
  --import \
  --noautoconsole

Key options:

  • --cpu host-passthrough exposes the host CPU model to the guest for the best performance.
  • bus=virtio and model=virtio use paravirtualized disk and network devices, much faster than emulated IDE or e1000 hardware.
  • --channel ... org.qemu.guest_agent.0 adds the channel the guest agent communicates through.
  • --graphics none gives the VM a serial console instead of a VNC display, which suits a headless server.

Check that the VM is running:

virsh list
 Id   Name   State
----------------------
 1    vm1    running

Step 8 - Connecting to the VM

Give cloud-init a minute to finish, then ask libvirt for the address the VM received from DHCP:

virsh domifaddr vm1
 Name       MAC address          Protocol     Address
-------------------------------------------------------------------------------
 vnet0      52:54:00:6b:3c:58    ipv4         192.168.122.45/24

Connect with SSH from the host using the address shown:

Inside the guest, confirm it runs under KVM:

systemd-detect-virt
kvm

You can also attach to the serial console with virsh console vm1 and leave it with Ctrl+].

Troubleshooting

  • KVM acceleration can NOT be used: virtualization is disabled in the firmware or, on a VPS, nested virtualization is not offered. Enable VT-x/AMD-V in the BIOS/UEFI and reboot.
  • virsh list shows nothing but VMs exist: you are connected to qemu:///session. Check with virsh uri and set LIBVIRT_DEFAULT_URI=qemu:///system as in Step 3.
  • Permission denied on /var/run/libvirt/libvirt-sock: your user is not in the libvirt group yet, or you have not logged out and back in.
  • virsh domifaddr shows no address: cloud-init is still running, or the VM is not on the default network. List DHCP leases with virsh net-dhcp-leases default and check the boot with virsh console vm1.

Conclusion

Your Ubuntu 24.04 server is now a KVM hypervisor managed by libvirt, and you created a VM from a cloud image with SSH access in a couple of minutes. Next, learn day-to-day VM management (start, stop, resize, snapshots) with virsh, set up a Linux bridge with netplan to put VMs on your public network, and add a dedicated storage pool on a separate disk for VM images.