Setting up a Debian KVM virtual machine on a headless server with libvirt
2022 January 18
This guide will refer to the physical machine as the host and the virtual machine installed in the host as the VM.
My host system while preparing this guide is a Debian Testing system without a GUI.
This guide does not use virt-manager, which requires a GUI.
Check CPU compatibility
Note: This step only applies to AMD and Intel CPUs. If you have an ARM, PowerPC, etc. CPU, I don't know how to tell if your CPU supports virtualization or not.
If your CPU doesn't support virtualization, you won't be able to follow this guide. Run this command to check:
user@host:~$ grep --color "svm\|vmx" /proc/cpuinfo
flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ht syscall nx mmxext fxsr_opt pdpe1gb rdtscp lm constant_tsc rep_good acc_power nopl nonstop_tsc cpuid extd_apicid aperfmper f pni pclmulqdq monitor ssse3 cx16 sse4_1 sse4_2 movbe popcnt aes xsave avx f16c lahf_lm cmp_legacy svm extapic cr8_legacy abm sse4a misalignsse 3dnowprefetch o svw ibs skinit wdt topoext perfctr_nb bpext ptsc perfctr_llc cpb hw_pstate ssbd vmmcall bmi1 xsaveopt arat npt lbrv svm_lock nrip_save tsc_scale flushbyasid dec odeassists pausefilter pfthreshold overflow_recov
Assuming your CPU supports virtualization, this should output a list of flags including either svm (on AMD) or vmx (on Intel) which indicates virtualization support. If there's no output, your CPU does not have virtualization support enabled.
If you don't get any output, check your BIOS settings to see if virtualization is just disabled. If your CPU doesn't support it, you won't be able to follow this guide.
Load kernel module
Check if there's a KVM module already loaded:
root@host:~# lsmod | grep --color kvm
kvm_amd 143360 13 kvm 1019904 1 kvm_amd irqbypass 16384 14 kvm ccp 106496 1 kvm_amd
Look for either kvm_amd (if you have an AMD CPU) or kvm_intel (for Intel). If neither shows up, issue the appropriate command to load the one you need:
root@host:~# modprobe kvm_amd
root@host:~# modprobe kvm_intel
We need these packages:
In Debian, qemu-kvm is actually a virtual package. It will instead install the appropriate package based on your CPU, in my case qemu-system-x86.
root@host:~# apt update && apt install --no-install-recommends qemu-kvm qemu-utils libvirt-clients libvirt-daemon-system virtinst dnsmasq
(We're using --no-install-recommends here to avoid pulling in unnecessary graphical packages.)
Make sure libvirtd is running and enabled at start:
root@host:~# systemctl enable --now libvirtd
Download and verify a Debian iso
Go to https://www.debian.org/download to download the newest netinstall iso.
Also download the SHA512SUMS and Signature files to the same directory.
If you don't already have the Debian CD signing key, you can get it from keyring.debian.org:
user@host:~$ gpg --keyserver keyring.debian.org --recv-keys DF9B9C49EAA9298432589D76DA87E80D6294BE9B
gpg: /home/user/.gnupg/trustdb.gpg: trustdb created gpg: key DA87E80D6294BE9B: public key "Debian CD signing key <firstname.lastname@example.org bian.org>" imported gpg: Total number processed: 1 gpg: imported: 1
Now, verify that the SHA512 sums were signed properly:
user@host:~$ gpg --verify-files SHA512SUMS.sign
gpg: assuming signed data in 'SHA512SUMS' gpg: Signature made Sat Dec 18 00:00:00 2021 UTC gpg: using RSA key DF9B9C49EAA9298432589D76DA87E80D6294BE9B gpg: Good signature from "Debian CD signing key <email@example.com>" [u nknown] gpg: WARNING: This key is not certified with a trusted signature! gpg: There is no indication that the signature belongs to the owner. Primary key fingerprint: DF9B 9C49 EAA9 2984 3258 9D76 DA87 E80D 6294 BE9B
(Key line bolded for emphasis.)
The warning line says that this key is not certified with a trusted signature because I haven't been able to verify that the key is authentic. Unfortunately, that's not something I can reasonably do.
Now, confirm that the SHA512 hash of the iso you downloaded is correct:
user@host:~$ sha512sum --ignore-missing -c SHA512SUMS
It should say the name of the file and that it's OK.
Create the VM
First, create the qcow2 file. The disk storage for the VM will be stored in this file.
root@host:~# qemu-img create -f qcow2 /var/lib/libvirt/images/my-vm-name.qcow2 10G
Replace "my-vm-name" and the size as needed.
Now here's the key step. We're going to create the VM, but we need to ensure that we can access it without needing to launch a GUI.
root@host:~# virt-install \
--name my-vm-name \
--description "Debian VM" \
--cpu host \
--location /path/to/debian/iso \
--graphics none \
--disk path=/var/lib/libvirt/images/my-vm-name.qcow2,size=10 \
--extra-args 'console=ttyS0,115200n8 serial' \
The --graphics none and --extra-args 'console=ttyS0,115200n8 serial' enable us to start the Debian installer in the terminal without launching a GUI.
The Debian installer should start. Do the next section before going through the installer.
Assign the VM an IP address
Open a second terminal on the host.
You should have a "default virtual network" by default. To confirm this,
root@host:~# virsh net-list --all
Name State Autostart Persistent -------------------------------------------- default active yes yes
If not, see the Networking page on the Libvirt Wiki.
When your VM is created, it should be assigned a MAC address. To get this MAC address, run
root@host:~# virsh dumpxml my-vm-name | grep "mac address"
Find subnet used by libvirt for VMs:
user@host:~$ ip a
[...] 10: virbr0:
mtu 1500 qdisc noqueue state UP group default qlen 1000 link/ether 52:54:00:3f:5a:65 brd ff:ff:ff:ff:ff:ff inet 192.168.122.1/24 brd 192.168.122.255 scope global virbr0 valid_lft forever preferred_lft forever [...]
(Look for virbr0 or something similar.) In this example, your VMs will identify the host machine as 192.168.122.1, and you can assign other 192.168.122.x addresses to VMs.
Assign an IP address to the new VM:
root@host:~# virsh net-update default add ip-dhcp-host "<host mac='52:54:00:b0:bc:b4' name='my-vm-name' ip='192.168.122.2' />" --live --config
Now go back to the terminal running the Debian installer and walk through the installation process as normal. You should be able to configure networking, but you might have to manually specify it, for instance:
- IP address: 192.168.122.2/24
- Gateway: 192.168.122.1
- Name server addresses: <whatever DNS server(s) you want to use>
What you install is up to you, but for this guide, you will only need the base system and an SSH server.
When the installer is finished, it should boot into a working Debian system. Make sure to set up ssh so you can connect to this VM easily!
Set up ssh
To ensure you can still connect to the VM after you set it up, make sure to set up ssh.
On the VM, install openssh-server if you didn't already:
root@vm:~# apt install openssh-server
It will generate keys and print their hashes.
Edit your sshd config:
root@vm:~# nano /etc/ssh/sshd_config
Uncomment and set the following two lines:
We want to use public key authentication, rather than password authentication. (Host-based authentication would be acceptable as well, since you should only be connecting from the host machine.)
Restart the ssh service to apply the new changes and ensure it's running:
root@vm:~# systemctl restart ssh
Also ensure ssh is enabled so it runs on startup:
root@vm:~# systemctl enable ssh
On the host device, generate an ssh key if you don't already have one:
user@host:~$ ssh-keygen -t ed25519
Generating public/private ed25519 key pair. Enter file in which to save the key (/home/user/.ssh/id_ed25519): Created directory '/home/user/.ssh'. Enter passphrase (empty for no passphrase): Enter same passphrase again: Your identification has been saved in /home/user/.ssh/id_ed25519 Your public key has been saved in /home/user/.ssh/id_ed25519.pub The key fingerprint is: SHA256:+aot/GJT+/1KmKuJB2IJkBhmzbOOYEBRGqZj3oddUZk user@host The key's randomart image is: +--[ED25519 256]--+ |+X=. ...o | |X o+ .E | |++ o . | |+oo.o . . | |o.o+ + S | | . .= . .. o | | . o o .+ . | | *ooo + | | .oB*oo oo. | +----[SHA256]-----+
(I've assumed defaults here, but you can change it if you want.)
Configure ssh on the host.
user@host:~$ nano ~/.ssh/config
Write a config something like this:
Host my-vm-name HostName 192.168.122.2 User username Port 22 IdentityFile ~/.ssh/id_ed25519 PubkeyAuthentication yes ServerAliveInterval 120
Most of this should be clear. Change it as needed. The ServerAliveInterval mitigates an annoying issue where ssh sessions freeze up because one side disconnects without notifying the other.
For security reasons, ssh files must be readable only by the owner. Change the permissions accordingly:
user@host:~$ chmod 600 ~/.ssh/config
Output your public key:
user@host:~$ cat ~/.ssh/id_ed25519.pub
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJy84ATUtHwlyepnXrU1vKsgfSmHRxmA5KOZlQLhhtg4 user@host
You'll need this in a moment.
On the VM, authorize the host's key.
First, create a .ssh folder and an authorized_keys file and set the appropriate permissions:
user@vm:~$ mkdir -p ~/.ssh && chmod 700 ~/.ssh && touch ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys
Now, copy your public key from above into the authorized_keys file:
user@vm:~$ echo "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJy84ATUtHwlyepnXrU1vKsgfSmHRxmA5KOZlQLhhtg4 user@host" >> ~/.ssh/authorized_keys
(Using >> will append the key to the file. Using only one > will overwrite the file. You can have multiple authorized keys, and they should be one per line.)
Now, get the VM's key fingerprint:
user@vm:~$ ssh-keygen -lf <(ssh-keyscan -t ed25519 localhost 2>/dev/null)
256 SHA256:H5WgbU/P3K0m8deOvXTHdAArAPMlfdN1HJVroLNP2b8 localhost (ED25519)
You'll need this in a moment.
On the host, try to ssh into the VM:
user@host:~$ ssh my-vm-name
The authenticity of host '192.168.122.2 (192.168.122.2)' can't be established. ED25519 key fingerprint is SHA256:H5WgbU/P3K0m8deOvXTHdAArAPMlfdN1HJVroLNP2b8. This key is not known by any other names Are you sure you want to continue connecting (yes/no/[fingerprint])?
The first time you connect to a new host, ssh will ask you to verify its fingerprint.
Ensure that the fingerprint matches the one from above and enter yes. Alternatively, you can directly enter the fingerprint from before.
If all went well, you should now be logged into the VM over ssh.
To be extra safe, you should reboot the VM and ensure that you can still ssh in.
Once you've confirmed that you can ssh in even after a reboot, it's safe to disconnect from the terminal where you were originally setting up the VM. Press Ctrl + ] to disconnect.
Basic virsh commands
You can use the virsh utility to manage VMs. Here are some useful commands...
View all VMs:
root@host:~# virsh list --all
Id Name State -------------------------- 12 my-vm-name running
Start a VM:
root@host:~# virsh start my-vm-name
Shutdown a VM:
root@host:~# virsh shutdown my-vm-name
Reboot a VM:
root@host:~# virsh reboot my-vm-name
Force shutdown a VM:
root@host:~# virsh destroy my-vm-name
Delete a VM:
root@host:~# virsh undefine my-vm-name
Edit a VM:
root@host:~# virsh edit my-vm-name
Congratulations! You should now be running a Debian virtual machine which you can ssh into.
This VM will be accessible from the private IP address 192.168.122.2 or whatever you set it as. You can use this to reverse-proxy, etc.
These pages were helpful: