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
sudoprivileges and an SSH key pair on that user (~/.ssh/id_ed25519.pub). Create one withssh-keygen -t ed25519if 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:virshandvirt-host-validate.virtinst:virt-installandvirt-cloneto create and copy VMs.xorriso: builds the small cloud-init ISO thatvirt-installattaches 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-passthroughexposes the host CPU model to the guest for the best performance.bus=virtioandmodel=virtiouse paravirtualized disk and network devices, much faster than emulated IDE or e1000 hardware.--channel ... org.qemu.guest_agent.0adds the channel the guest agent communicates through.--graphics nonegives 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 listshows nothing but VMs exist: you are connected toqemu:///session. Check withvirsh uriand setLIBVIRT_DEFAULT_URI=qemu:///systemas in Step 3.Permission deniedon/var/run/libvirt/libvirt-sock: your user is not in thelibvirtgroup yet, or you have not logged out and back in.virsh domifaddrshows no address: cloud-init is still running, or the VM is not on thedefaultnetwork. List DHCP leases withvirsh net-dhcp-leases defaultand check the boot withvirsh 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.
