How to install KVM on CentOS 8 Headless Server

How can I install KVM (Kernel-based Virtual Machine) on a CentOS 8 (CentOS Enterprise Linux) headeless server? How do I set up KVM on a CentOS 8 and use cloud images/cloud-init for installing guest VM?

Tutorial details
Difficulty level Intermediate
Root privileges Yes
Requirements CentOS 8 Linux
Est. reading time 15 minutes
The Linux Kernel-based Virtual Machine (KVM) is free and opensource virtualization software for CentOS 8. KVM turn your server into a hypervisor. This page shows how to set up and manage a virtualized environment with KVM in CentOS 8. It also described how to install and administer Virtual Machines (VMs) on a physical server using the command-line. Make sure that Virtualization Technology (VT) is enabled in your server’s BIOS. You can also run the following command to test if CPU Support Intel VT and AMD-V Virtualization tech:
$ lscpu | grep Virtualization
## Test for both Intel/AMD CPU that support KVM ##
$ sort -u <<<$(egrep -o -i --color "vmx|svm" /proc/cpuinfo)
Virtualization: VT-x

The KVM only supports Intel/AMD 64 bit and IBM z13, Power 8/9 CPU, and later architecture of your host VMs.

Steps to install KVM on CentOS 8 headless remote server

Please use the su command or sudo command to become root user:
$ sudo -i
## OR ##
$ su -

Step 1: Installing kvm

Run the following dnf command/yum command:
# dnf module install virt
How to install KVM on CentOS 8 Headless Server
Then install tools to provision new virtual machines (VMs):
# dnf install virt-install virt-viewer libguestfs-tools
CentOS 8 install the virtualization packages using dnf
Enable and start the libvirtd service using the systemctl command:
# systemctl enable libvirtd.service
# systemctl start libvirtd.service
# systemctl status libvirtd.service

Enable and start the libvirtd service on CentOS 8 Linux using the systemctl

Step 2: Verify your kvm installation

Make sure KVM Linux kernel module (driver) loaded using lsmod command and grep command:
# lsmod | grep -i kvm

Step 3: Configure bridged networking

By default, dnsmsq based network bridge configured by libvirtd called virbr0. You can verify that with the following simple commands:
# virsh net-info default
# nmcli device
# nmcli connection show

The libvirtd uses a lightweight DHCP and caching DNS server named dnsmasq. We can see config file including IP ranges either using the cat command or grep command/egrep command:
# cat /var/lib/libvirt/dnsmasq/default.conf
# egrep '^(dhcp-range|interface)' /var/lib/libvirt/dnsmasq/default.conf
## use the ip command to verify info about the virbr0 ##
# ip a show virbr0
# ip r

KVM libvirtd virbr0 bridge on CentOS 8

Step 4: Configure bridged networking

If you want your VMs available to other servers on your LAN, set up a network bridge on the server that connected to your LAN. Update your nic config files. Here is my pre-configured br0 interface enslaved with eno1 Ethernet:
# vi /etc/sysconfig/network-scripts/ifcfg-br0
br0 config:

## my lan ##
## Bridge br0 config, only IPv4 and no IPv6 here for now ##

And config for eno1 Ethernet:
# vi /etc/sysconfig/network-scripts/ifcfg-bridge-slave-eno1
eno1 config:


Restart the networking service (warning ssh command will disconnect, it is better to reboot the Linux box):
# systemctl restart NetworkManager.service
## OR ##
# nmcli con up br0
# nmcli connection delete eno1

Verify it with the nmcli command # nmcli device
Sample outputs:

DEVICE      TYPE      STATE      CONNECTION        
br0         bridge    connected  br0               
virbr0      bridge    connected  virbr0            
eno1        ethernet  connected  bridge-slave-eno1 
lo          loopback  unmanaged  --                
virbr0-nic  tun       unmanaged  --                
wlp1s0      wifi      unmanaged  --  

See “CentOS 8 add network bridge (br0) with nmcli command” for more information.

Step 5: Create your first virtual machine/guest VM

I am going to create a brand new CentOS 8.x VM. First, grab CentOS 8.x latest ISO image using the wget command/lftp command # cd /var/lib/libvirt/boot/
# wget

Verify the ISO image using the sha256sum command:
# wget
# sha256sum --ignore-missing -c CHECKSUM

Create CentOS 8.x VM

In this following example, I creating CentOS 8.x VM with 1GB RAM, 1 CPU core, 1 nics and 20GB hard disk space, enter:
# virt-install \
--virt-type=kvm \
--name centos8-vm \
--memory 1024 \
--vcpus=1 \
--os-variant=rhel8.1 \
--cdrom=/var/lib/libvirt/boot/CentOS-8.1.1911-x86_64-boot.iso \
--network=bridge=br0,model=virtio \
--graphics vnc \
--disk path=/var/lib/libvirt/images/centos8.qcow2,size=20,bus=virtio,format=qcow2

To configure VNC and complete CentOS 8 VM installation, ssh from another terminal and type:
# virsh dumpxml centos8-vm | grep vnc
    <graphics type='vnc' port='5900' autoport='yes' listen=''>

Then down the port value (i.e. 5900). You need to use an SSH client to set up tunnel and a VNC client to access the remote VM’s VNC display. Type the following SSH port forwarding command from your Linux and Unix client/MacBook pro desktop:
{vivek@linux-desktop:~ }$ ssh vivek@ -L 5900:
Now that you have ssh tunnel established, point your VNC client at (localhost) address and port 5900 as follows:
VNC client for gust vm installation
You should see the CentOS Linux 8 guest installation screen as follows:
Welcome to CentOS Linux 8 guest installation
Now follow on-screen instructions and install CentOS 8. Once installed, go ahead and click the reboot button. The remote server closed the connection to our VNC client. You can reconnect via KVM client to configure the rest of the server, including SSH-based sessions, firewalls, networking, and much more.

How to create RHEL 8.x VM

Make sure you have rhel.iso stored locally. In this example, I am going to create RHEL 8.x VM with 2GB RAM, 2 CPU core, 1 NIC and 40GB disk space, enter:
# virt-install \
--virt-type=kvm \
--name rhe8-server \
--memory 2048 \
--vcpus=2 \
--os-variant=rhel8.1 \
--cdrom=/var/lib/libvirt/boot/rhel-server.iso \
--network=bridge=br0,model=virtio \
--graphics vnc \
--disk path=/var/lib/libvirt/images/rhel8.qcow2,size=40,bus=virtio,format=qcow2

To get vnc port info, login from another terminal over the ssh and type:
$ sudo virsh dumpxml rhel8-server | grep vnc
    <graphics type='vnc' port='5906' autoport='yes' listen=''>

You need to use an SSH client to setup tunnel and a VNC client to access the remote vnc VM display. Type the following SSH port forwarding command from your client/desktop:
$ ssh vivek@ -L 5906:
Once ssh tunnel established, point your VNC client at (localhost) address and port 5906 to continue with RHEL 8.x installation.

Step 6: Build guest images using virt-builder

One can build virtual machine quickly on a CentOS 8 using the virt-builder command.

List images

# virt-builder --list
# virt-builder --list | egrep -i 'debian|ubuntu'
# virt-builder --list | egerp -i centos

Build Ubuntu 18.04 LTS vm

First, set shell variables:

vm="ubuntu-vm1"                  # VM name
os="ubuntu-18.04"                # OS    
tz="Asia/Kolkata"                # Time zone
ram="1024"                       # VM RAM 
disk="10G"                       # VM Disk Size
vcpu="1"                         # Number of Virtual CPUs
key=~/.ssh/            # SSH Pub key
pwd="Encrypted_PASSWORD_HERE"    # Use 'mkpasswd --method=sha512crypt' to create Encrypted password
bridge="br0"                     # Network bridge name such as 'br0' or 'virbr0'
ostype="ubuntu18.04"             # Run 'osinfo-query os' to get exact varient

Now build virtual machine images quickly as per variables:
# virt-builder "${os}" \
--hostname "${vm}" \
--network \
--timezone "${tz}" \
--size=${disk} \
--format qcow2 -o /var/lib/libvirt/images/${vm}-disk01.qcow2 \
--update \
--firstboot-command "dpkg-reconfigure openssh-server" \
--firstboot-command "useradd -p ${pwd} -s /bin/bash -m -d /home/vivek -G sudo vivek" \
--edit '/etc/default/grub:s/^GRUB_CMDLINE_LINUX_DEFAULT=.*/GRUB_CMDLINE_LINUX_DEFAULT="console=tty0 console=ttyS0,115200n8"/' \
--ssh-inject "root:file:${key}" \
--run-command update-grub

Sample outputs:

[   2.0] Downloading:
[   2.4] Planning how to build this image
[   2.4] Uncompressing
[   7.9] Resizing (using virt-resize) to expand the disk to 10.0G
[  20.8] Opening the new disk
[  24.4] Setting a random seed
virt-builder: warning: random seed could not be set for this type of guest
[  24.4] Setting the hostname: ubuntu-vm1
[  25.1] Setting the timezone: Asia/Kolkata
[  25.2] Updating packages
[ 138.7] Installing firstboot command: dpkg-reconfigure openssh-server
[ 138.8] Installing firstboot command: useradd -p $6$XoUElABFFfTFr4/f$dGYZ2MCb2QcMuKQ2RmE./U0v7mhr2LFd4rbvOMdbjPCWwiyOMuirQagJ.9hBobz9Dy61AXS8oeTabem/H5YhB1 -s /bin/bash -m -d /home/vivek -G sudo vivek
[ 138.8] Editing: /etc/default/grub
[ 138.9] SSH key inject: root
[ 139.7] Running: update-grub
[ 139.9] Setting passwords
virt-builder: Setting random password of root to SzzVUrAhaYRUiVJj
[ 140.6] Finishing off
                   Output file: /var/lib/libvirt/images/ubuntu-vm1-disk01.qcow2
                   Output size: 10.0G
                 Output format: qcow2
            Total usable space: 9.8G
                    Free space: 7.9G (81%)

Please note down random root passwoed. Now our custom VM image has been built with given options. Let us install VM:
# virt-install --import --name "${vm}" \
--ram "${ram}" \
--vcpu "${vcpu}" \
--disk path=/var/lib/libvirt/images/${vm}-disk01.qcow2,format=qcow2 \
--os-variant "${ostype}" \
--network=bridge=${bridge},model=virtio \

List VM:
# virsh list
Log in using console
# virsh console ${vm}
Since we injected the ssh key, enter:
## this will only work if your br0 dhcpd also provide name resoution via dns ##
# host $vm
# ssh root@vm-ip-here
# ssh root@${vm}

virsh-builder for building guest vms quickly
We can use virt-builder to build a variety of VMs for local or cloud use, usually within a few minutes or less. Then we use virt-install to install KVM on CentOS 8 headless server.

Step 7: Using cloud images

The manual installation method is okay for learning purposes or for building a single VM. But do you need to deploy lots of VMs? Try cloud images. You can modify pre-built cloud images as per your needs. For example, we can add users, install ssh keys, setup time zone, and more using Cloud-init, which is the defacto multi-distribution package that handles early initialization of a cloud instance. Let us see how to create CentOS 8 VM using cloud images with 1024MB ram, 20GB disk space, and one vCPU.

Download CentOS 8 cloud image

# cd /var/lib/libvirt/boot
# wget
# wget
# sha256sum --ignore-missing -c CHECKSUM

Use the mkdir command to create required directories

# D=/var/lib/libvirt/images
# VM=centos8-vm1 ## your vm goes name ##
# mkdir -vp $D/$VM
mkdir: created directory '/var/lib/libvirt/images/centos8-vm1'

Create meta-data file

# cd $D/$VM
# vi meta-data

Append the following config:

instance-id: centos8-vm1
local-hostname: centos8-vm1

Create user-data file

I am going to login into VM using ssh keys. So make sure you have ssh-keys in place:
# ssh-keygen -t ed25519 -C "CentOS 8 host server login ssh key"
ssh keys for CentOS 8 headless server
See “How To Set up SSH Keys on a Linux / Unix System” for more info. Edit user-data as follows:
# cd $D/$VM
# vi user-data

Add as follows (replace hostname, users, ssh-authorized-keys as per your setup):

# Hostname management
preserve_hostname: False
hostname: centos8-vm1
fqdn: centos8-vm1.sweet.home
# Users
    - default
    - name: vivek
      groups: ['wheel']
      shell: /bin/bash
      sudo: ALL=(ALL) NOPASSWD:ALL
        - ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIO4Owk8inCz5ZnhWSiP2Y5wfVKTFOLTFOJ0K/sC2egDF CentOS 8 host server login ssh key
# Configure where output will go
  all: ">> /var/log/cloud-init.log"
# configure interaction with ssh server
ssh_genkeytypes: ['ed25519', 'rsa']
# Install my public ssh key to the first user-defined user configured
# in cloud.cfg in the template (which is centos for CentOS cloud images)
  - ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIO4Owk8inCz5ZnhWSiP2Y5wfVKTFOLTFOJ0K/sC2egDF CentOS 8 host server login ssh key
# set timezone for VM
timezone: Asia/Kolkata
# Remove cloud-init 
  - systemctl stop NetworkManager.service && systemctl start NetworkManager.service
  - dnf -y remove cloud-init

Validate user-data for syntax errors if any:
# cloud-init devel schema --config-file user-data
Valid cloud-config file user-data

Copy cloud image

# cd $D/$VM
# cp -v /var/lib/libvirt/boot/CentOS-8-GenericCloud-8.1.1911-20200113.3.x86_64.qcow2 $VM.qcow2
'/var/lib/libvirt/boot/CentOS-8-GenericCloud-8.1.1911-20200113.3.x86_64.qcow2' -> 'centos8-vm1.qcow2'

Create 20GB disk image

# cd $D/$VM
# qemu-img create -f qcow2 -o preallocation=metadata $ 20G
# virt-resize --quiet --expand /dev/sda1 $VM.qcow2 $
# mv -f $ $VM.qcow2
# ls -l

virt-resize on CentOS 8 headless KVM server

Creating a cloud-init ISO file

# mkisofs -o $VM-cidata.iso -V cidata -J -r user-data meta-data

I: -input-charset not specified, using utf-8 (detected in locale settings)
Total translation table size: 0
Total rockridge attributes bytes: 331
Total directory bytes: 0
Path table size(bytes): 10
Max brk space used 0
183 extents written (0 MB)

Creating a pool

# virsh pool-create-as --name $VM --type dir --target $D/$VM
Pool centos8-vm1 created

Installing a CentOS 8 VM via cloud image

# cd $D/$VM
# virt-install --import --name $VM \
--memory 1024 --vcpus 1 --cpu host \
--disk $VM.qcow2,format=qcow2,bus=virtio \
--disk $VM-cidata.iso,device=cdrom \
--network bridge=br0,model=virtio \
--os-variant=rhel8.1 \
--graphics spice \

Delete unwanted files:
# cd $D/$VM
# virsh change-media $VM sda --eject --config
Successfully ejected media.
## use the rm command to deleted files ##
# rm -v meta-data user-data $VM-cidata.iso

rm: remove regular file 'meta-data'? y
removed 'meta-data'
rm: remove regular file 'user-data'? y
removed 'user-data'
rm: remove regular file 'centos8-vm1-cidata.iso'? y
removed 'centos8-vm1-cidata.iso'

Find out an IP address of KVM VM named centos8-vm1

If you are using the ‘br0’ use the host command:
# host $VM
centos8-vm1.sweet.home has address

To find out an ip address of Linux KVM guest virtual machine when using the default virbr0 bride interface:
# virsh net-dhcp-leases default

Verification – Log in to centos8-vm

Use the ssh command:
# ssh vivek@$VM
# ssh vivek@

CentOS 8 Cloud Image KVM Verification

A note about useful virsh KVM management commands

Let us see some useful commands for managing VMs.

List all VMs

# virsh list --all

Get VM info

# virsh dominfo vmName
# virsh dominfo centos8-vm1

Stop/shutdown a VM

# virsh shutdown centos8-vm1

Start VM

# virsh start centos8-vm1

Mark VM for autostart at CentOS 8 server boot time

# virsh autostart centos8-vm1

Reboot (soft & safe reboot) VM

# virsh reboot ubuntu-vm1

Reset (hard reset/not safe) VM [last resort]

# virsh reset ubuntu-vm1

Delete VM

# virsh shutdown centos8-vm1
# virsh undefine centos8-vm1
# virsh pool-destroy centos8-vm1
# D=/var/lib/libvirt/images
# VM=centos8-vm1.img
# rm -ri $D/$VM

To see a complete list of virsh command type

# virsh help | less
# virsh help | grep reboot


In this tutorial, you learned how to install KVM on CentOS 8 Linux server and provision new gust VM using the virt-install. Further, you learned quickly building new virtual machines using virt-builder. Please libvirt docs here and Linux KVM home docs here. KVM software has many more options. In other words, read the following man pages:
man virt-install
man virt-builder
man nmcli
man ip
man bridge

🐧 Get the latest tutorials on Linux, Open Source & DevOps via RSS feed or Weekly email newsletter.

🐧 2 comments so far... add one

CategoryList of Unix and Linux commands
Disk space analyzersncdu pydf
File Managementcat
FirewallAlpine Awall CentOS 8 OpenSUSE RHEL 8 Ubuntu 16.04 Ubuntu 18.04 Ubuntu 20.04
Network UtilitiesNetHogs dig host ip nmap
OpenVPNCentOS 7 CentOS 8 Debian 10 Debian 8/9 Ubuntu 18.04 Ubuntu 20.04
Package Managerapk apt
Processes Managementbg chroot cron disown fg jobs killall kill pidof pstree pwdx time
Searchinggrep whereis which
User Informationgroups id lastcomm last lid/libuser-lid logname members users whoami who w
WireGuard VPNAlpine CentOS 8 Debian 10 Firewall Ubuntu 20.04
2 comments… add one
  • Jane Apr 22, 2021 @ 17:49

    Where did you get the UUID for br0 and ifcfg-bridge-slave-eno1?

Leave a Reply

Your email address will not be published.

Use HTML <pre>...</pre> for code samples. Still have questions? Post it on our forum