Add Controller or NVMe to VM

Disk Manager · VM~15 minView script

Pass a whole SATA / SAS host bus adapter (HBA) or NVMe device into a Proxmox VM via PCI passthrough. ProxMenux checks and — with your consent — enables IOMMU, enumerates the passthrough-eligible devices, filters out conflicts and wires the assignment with qm set --hostpci.

What this does (and does not) do

This is device-level passthrough: the PCIe card leaves the host kernel's view and is bound to vfio-pci. The VM sees the controller itself — not the individual disks behind it — and loads its own driver for that exact model. Every disk attached to that controller follows it automatically, with native SMART data, full firmware features and no Proxmox layer in between.

How the script runs

The flow has two phases with clear separation between "collecting information, checking IOMMU and listing candidates" and "actually attaching the PCI device". Until the final confirmation nothing is written to the VM config.

┌─────────────────────────────────────────────┐
│  PHASE 1 — Detect, validate, plan           │
│  (nothing touched yet)                      │
└──────────────────┬──────────────────────────┘
                   ▼
      qm list — user picks target VM
                   │
                   ▼
      IOMMU status on the running kernel
      ├─ /sys/kernel/iommu_groups/* exists?
      │
      ├─ Yes → IOMMU active, continue
      │
      └─ No  → cmdline check + offer to enable
            CPU vendor detect (cat /proc/cpuinfo)
            ├─ Intel → write intel_iommu=on
            └─ AMD   → write amd_iommu=on
            Into:
            ├─ /etc/kernel/cmdline  (systemd-boot)
            └─ /etc/default/grub    (GRUB)
            + update-initramfs -u -k all
            + offer reboot now
            ├─ reboot accepted → reboot
            └─ reboot declined → abort
                 (re-run after reboot)
                   │
                   ▼
      Enumerate storage-class PCI devices
      lspci -Dnn filtered by class:
      ├─ SATA / SAS / SCSI / NVMe controllers
      ├─ Resolve IOMMU group via /sys path
      └─ For HBAs: list disks currently behind
                   │
                   ▼
      Conflict / eligibility filter
      ├─ Already in this VM's hostpci? → hide
      ├─ Already in another VM's hostpci?
      │    → block (shown with owner VM id)
      ├─ Carries the Proxmox root disk
      │    or any disk referenced by an LXC
      │    → block
      └─ Shared IOMMU group
         with non-storage members?
            → show ⚠ warning inline
                   │
                   ▼
      User selects device(s) via checklist
                   │
                   ▼
      Summary:
      (VM + each PCI device + IOMMU group
       membership + reboot status)
                   │
   ┌──────── Cancel   OR   Confirm ────┐
   ▼                                   ▼
Exit, nothing        ┌─────────────────┴─────────────────┐
was changed          │  PHASE 2 — Apply                   │
                     └─────────────────┬─────────────────┘
                                       ▼
                       Host side (once per session):
                       ├─ Add vfio-pci to /etc/modules
                       ├─ Append the device vendor:device
                       │  IDs to /etc/modprobe.d/vfio.conf
                       └─ update-initramfs -u -k all
                       (so the device is bound to vfio-pci
                        at next boot, not the native driver)
                                       │
                                       ▼
                       For each selected device:
                       ├─ Find next free hostpciN slot
                       │   (scans qm config)
                       └─ qm set <VMID> --hostpciN \
                             <BDF>,pcie=1
                             (e.g. 0000:01:00.0,pcie=1)
                                       │
                                       ▼
                       Verify: qm config <VMID> shows
                       the new hostpciN entries
                                       │
                                       ▼
                       If IOMMU was just enabled:
                       └─ reminder to reboot before
                          starting the VM
                                       │
                                       ▼
                       Guest on next boot sees the
                       controller directly + every disk
                       behind it (full SMART, native
                       firmware features, no Proxmox layer)

IOMMU groups — why the whole card leaves at once

PCI passthrough is done per IOMMU group, which is the smallest unit the CPU's IOMMU can isolate. Everything inside a group moves together. If your SATA controller shares its IOMMU group with the USB controller, passing the SATA card to a VM also takes the USB ports with it — often undesirable.

          Host PCIe bus — grouped by IOMMU
                      │
                      ▼
    ┌─────────────────────────────────────────┐
    │  Group 12                               │
    │  ────────                               │
    │  00:17.0   SATA HBA                     │
    │    └── sda sdb sdc sdd                  │
    │                                         │
    │  Pass-through takes:                    │
    │    the HBA + every disk on it           │
    │                                         │
    │  ✓ clean — no extra members in group    │
    └──────────────────┬──────────────────────┘
                       │
                       ▼
    ┌─────────────────────────────────────────┐
    │  Group 13                               │
    │  ────────                               │
    │  01:00.0   NVMe controller              │
    │                                         │
    │  Pass-through takes:                    │
    │    the NVMe controller itself           │
    │                                         │
    │  ✓ clean — NVMe alone in its group      │
    └──────────────────┬──────────────────────┘
                       │
                       ▼
    ┌─────────────────────────────────────────┐
    │  Group 14                               │
    │  ────────                               │
    │  02:00.0   SATA HBA                     │
    │    └── sde sdf                          │
    │  02:00.1   USB 3.0 controller           │
    │                                         │
    │  Pass-through takes:                    │
    │    SATA HBA + USB 3.0 controller        │
    │    (whole group leaves together)        │
    │                                         │
    │  ⚠ shared group — the USB ports will    │
    │    also leave the host. Review whether  │
    │    that is acceptable before confirming.│
    └─────────────────────────────────────────┘

ProxMenux shows the IOMMU group next to each device and warns you when a group carries more than just the controller you want. You can still proceed if the extra devices in the group are acceptable (all disk controllers, for example), or back out if they are not.

Prerequisites

  • CPU and motherboard support VT-d (Intel) or AMD-Vi, and it is enabled in firmware (BIOS/UEFI). ProxMenux can enable the kernel side for you, but it cannot flip the firmware switch.
  • The target VM is powered off before attaching PCI devices.
  • The controller/NVMe is not the one holding the Proxmox root filesystem. The script hides anything in use by the host.
  • Guest OS has drivers for the controller model. Linux covers virtually every HBA out of the box; Windows needs the vendor driver installed before you boot with the controller attached.

Live migration is not possible

A VM with PCI passthrough is tied to the node that physically holds the card. Live migration across nodes will fail. Plan backups / replication accordingly.

Step-by-step

Step 1

Pick the target VM

ProxMenux reads qm list and shows every VM. Pick the one that will receive the controller.

VM selection dialog
VM selection dialog
Step 2

IOMMU check

If IOMMU is not enabled on the kernel cmdline, ProxMenux offers to enable it. It edits /etc/kernel/cmdline (for systemd-boot) or /etc/default/grub (for GRUB) and appends the correct parameter for your CPU:

  • intel_iommu=on for Intel CPUs.
  • amd_iommu=on for AMD CPUs.

A reboot is required after this change. The script prompts you to reboot now or later; the passthrough cannot continue until the new kernel cmdline is active.

IOMMU Required dialog
IOMMU Required — offer to enable and reboot
Step 3

Device enumeration

ProxMenux lists every storage controller (SATA / SAS HBA) and every NVMe device on the PCI bus. For each device it shows:

  • PCI address (00:17.0, 01:00.0, …).
  • Vendor / device description.
  • IOMMU group — and a ⚠ warning if the group is shared with other, unrelated devices.
  • The disks currently behind the controller (so you know what will follow it to the VM).
Controller / NVMe device enumeration
Controller / NVMe device enumeration with IOMMU group info
Step 4

Conflict detection

The script blocks devices that are clearly unsafe: already assigned to another VM (via hostpci in that VM's config), in use by the host (root disk controller), or sitting in an IOMMU group whose other members the script cannot identify safely. Eligible devices are available for selection.

Conflict detection warning
Conflict detection — device blocked with reason
Step 5

Attach and finalise

For every selected device ProxMenux runs qm set &lt;VMID&gt; --hostpciN &lt;pci-addr&gt;,pcie=1, choosing the next free hostpci slot. If IOMMU was enabled during this session you are reminded to reboot before starting the VM.

Assignment summary
Assignment summary — hostpciN slots and PCI addresses attached

Manual equivalent

# 1. verify IOMMU is active
dmesg | grep -iE "DMAR|IOMMU" | head
ls /sys/kernel/iommu_groups/

# 2. list IOMMU groups and their members
for d in /sys/kernel/iommu_groups/*/devices/*; do
  n=$(basename "$d"); g=$(dirname "$(dirname "$d")")
  printf 'group %3d  %s  %s\n' "$(basename "$g")" "$n" \
    "$(lspci -s "$n" | cut -d' ' -f2-)"
done | sort -n

# 3. attach a storage controller at PCI 0000:00:17.0 to VM 101
qm set 101 --hostpci0 0000:00:17.0,pcie=1

# 4. verify
qm config 101 | grep ^hostpci

Troubleshooting

IOMMU enabled but no groups under /sys/kernel/iommu_groups/

Reboot is required for the kernel cmdline to take effect. If the groups are still missing after reboot, VT-d / AMD-Vi is probably disabled in firmware — check the BIOS/UEFI setup.

VM fails to start with "hostpciN: device busy"

Another process still holds the PCI device. Most common causes: a previous VM that crashed without releasing it, or the host kernel reclaimed it on the last boot. Run lspci -nnk -s &lt;addr&gt; to see which driver is bound; it should be vfio-pci. A full host reboot usually clears this.

Guest sees the controller but not the disks

The controller model lacks an in-guest driver. Linux almost never hits this; Windows does — install the vendor driver (e.g. LSI / Broadcom for older SAS HBAs) while the card is detached, then reboot with the card attached.

Shared IOMMU group also exports an unrelated device

Options: (a) move the card to a different PCIe slot to change its group, (b) enable ACS override in the kernel to split groups (security trade-off — research before applying), or (c) pick a different card in a cleaner group.

Related