Pass a GPU through to a VM
Reserve a discrete GPU for VFIO on a Debian host and attach it to a libvirt VM from the Cockpit console.
Overview
This guide explains how to reserve a discrete GPU for VFIO on a Debian 13 (trixie) host and attach it to a virtual machine managed through Cockpit.
The GPU has to be claimed by vfio-pci at boot, before any ordinary driver
touches it. That is a host-level change requiring a reboot, so the host is
configured first and the VM comes second.
Before you start
Before you pass a GPU through, ensure:
- Debian 13 is installed and booted in UEFI mode. The steps assume the 6.12 kernel trixie ships.
- Cockpit and libvirt are working — see Set up Cockpit.
- The host has a second display adapter — an integrated GPU or a spare card — driving its console. A GPU handed to a VM stops being available to the host, so it cannot be the one showing the host's own display.
- Virtualisation and IOMMU are enabled in firmware:
SVM ModeandIOMMUon AMD,VT-xandVT-don Intel. Set the primary display to the integrated GPU while you are in the BIOS setup.
The worked examples come from an AMD Ryzen 7 7800X3D host whose integrated Raphael GPU drives the console and whose GeForce RTX 3060 is passed through. Your addresses and device IDs will differ; every command that produces them is shown.
Pass a GPU through to a VM
Reserve the GPU for VFIO
-
Confirm the IOMMU is active.
bash ls /sys/class/iommu/An AMD host prints
ivhd0; an Intel host printsdmar0. An empty directory means the IOMMU is off in firmware — go back to the BIOS setup, because nothing below works without it. -
Identify the GPU's PCI functions and device IDs.
bash lspci -nnk | grep -EA3 'VGA|3D controller|Audio device'A graphics card is at least two PCI functions — the display controller and an HDMI/DisplayPort audio controller — and both have to go to the guest:
01:00.0 VGA compatible controller [0300]: NVIDIA Corporation GA104 [GeForce RTX 3060] [10de:2487] (rev a1) Kernel driver in use: nouveau Kernel modules: nvidiafb, nouveau 01:00.1 Audio device [0403]: NVIDIA Corporation GA104 High Definition Audio Controller [10de:228b] (rev a1) Kernel driver in use: snd_hda_intel Kernel modules: snd_hda_intelNote both
[vendor:device]IDs — here10de:2487and10de:228b— and the driver names on theKernel modules:lines. Both are used in step 4. -
Confirm the GPU sits in an IOMMU group of its own.
bash for d in /sys/kernel/iommu_groups/*/devices/*; do n=${d#*/iommu_groups/} echo "group ${n%%/*}: $(lspci -nns "${d##*/}")" done | sort -VFind the lines for your card. The group must hold the card's own functions and nothing else:
group 12: 01:00.0 VGA compatible controller [0300]: NVIDIA Corporation GA104 [GeForce RTX 3060] [10de:2487] (rev a1) group 12: 01:00.1 Audio device [0403]: NVIDIA Corporation GA104 High Definition Audio Controller [10de:228b] (rev a1)An IOMMU group is the smallest unit that can be isolated, so an NVMe drive or USB controller sharing the group would have to be passed through too. Move the card to a different PCIe slot and re-check rather than reaching for an ACS override kernel patch, which defeats the isolation the group exists to provide.
-
Bind the card to
vfio-pciat module load time.Substitute your IDs from step 2 into
ids=, and add asoftdepline for every driver named on the card'sKernel modules:lines:bash sudo tee /etc/modprobe.d/vfio.conf <<'EOF' options vfio-pci ids=10de:2487,10de:228b softdep nouveau pre: vfio-pci softdep nvidiafb pre: vfio-pci softdep snd_hda_intel pre: vfio-pci EOFThe
softdeplines are the part people leave out, and leaving them out fails quietly.ids=only tellsvfio-pciwhich devices to claim when it loads — whichever driver reaches a device first keeps it.snd_hda_intelloads early for the host's onboard audio and takes the GPU's audio function on the way past, leaving the display controller onvfio-pciand the audio function onsnd_hda_intel. libvirt will try to wrest that function back when the VM starts, and fails to when the host has the device open — so the passthrough works until the day it does not. -
Add the VFIO modules to the initramfs.
bash printf 'vfio\nvfio_iommu_type1\nvfio_pci\n' | sudo tee -a /etc/initramfs-tools/modulesA
softdepcan only prefer a module that exists in the same context, so these have to be in the initramfs alongside the drivers they are racing. Older guides also listvfio_virqfd; that module was folded into the VFIO core in Linux 6.2 and does not exist on Debian 13's kernel, so leave it out. -
Turn on IOMMU passthrough mode on the kernel command line.
Edit
/etc/default/gruband extendGRUB_CMDLINE_LINUX_DEFAULT:bash GRUB_CMDLINE_LINUX_DEFAULT="quiet amd_iommu=on iommu=pt"On an Intel host, use
intel_iommu=on iommu=ptinstead.iommu=ptputs devices the host still owns into passthrough mode, keeping their DMA off the slower translated path.Do not add
vfio-pci.ids=here as well. It does the same job as theoptionsline in step 4 and leaves two places to edit next time the card changes. -
Rebuild the initramfs and the boot configuration.
bash sudo update-initramfs -u -k all sudo update-grub -
Reboot, then confirm the whole card landed on
vfio-pci.bash sudo rebootOnce it is back, substituting your card's bus address:
bash lspci -nnk -s 01:00.Every function must report
vfio-pci:01:00.0 VGA compatible controller [0300]: NVIDIA Corporation GA104 [GeForce RTX 3060] [10de:2487] (rev a1) Kernel driver in use: vfio-pci Kernel modules: nvidiafb, nouveau 01:00.1 Audio device [0403]: NVIDIA Corporation GA104 High Definition Audio Controller [10de:228b] (rev a1) Kernel driver in use: vfio-pci Kernel modules: snd_hda_intelIf a function still shows its ordinary driver, the
softdepfor that driver is missing or misspelled — the name invfio.confmust match theKernel modules:line exactly, underscores and all.
Attach the GPU to a VM
-
Create the VM from Virtual machines → Create VM.
Fill in the name, installation source, storage, and memory, then choose Create and edit. Do not tick Immediately start VM: the firmware cannot be changed once the guest has booted, and the next step has to happen first.
-
Switch the firmware to UEFI.
On the VM's Overview, click the Firmware value — it reads
BIOSon a new VM — selectUEFI, and save. This is what theovmfpackage provides, and it is a one-way door: after the first boot the field is fixed. -
Add the GPU under Host devices → Add host device.
Set Type to
PCI, tick every function of the card — the display controller and its audio controller both — and click Add. The VM must be shut off. Leaving the audio controller behind costs the guest its HDMI and DisplayPort audio, and if that function is still held by a host driver the VM fails to start with agroup is not viableerror, because VFIO hands over an IOMMU group whole or not at all. -
Start the VM and install the guest operating system.
libvirt detaches the devices from the host and hands them to QEMU as it starts. If the VM refuses to start with an error about a device being in use, re-check step 8 of the previous sub-task.
-
Confirm the guest sees the card.
In a Linux guest:
bash lspci -nn | grep -i vgaThe card appears with the same vendor and device IDs it had on the host. In a Windows guest, look under Display adapters in Device Manager.
-
Install the vendor's GPU driver inside the guest.
Real display output now comes from the card's own ports. Cockpit's built-in console keeps showing QEMU's emulated display adapter, which is expected and not a sign the passthrough failed — plug a monitor into the card to see the guest's actual output.
GeForce cards no longer need the old
<kvm><hidden state='on'/></kvm>domain workaround: NVIDIA has supported virtualisation on GeForce since driver 465.89. If you meet the "Code 43" error, the guest driver predates that release.
See also
- libvirt: host device assignment
- Linux kernel: VFIO documentation
- ArchWiki: PCI passthrough via OVMF
- Set up NVIDIA GPU passthrough — the host half of this page as one paste
- Set up Cockpit
- First boot