Gaming Setup (Sunshine + Moonlight)
Set up a headless Linux gaming VM with GPU passthrough, a virtual display, and game streaming via Sunshine and Moonlight. No physical monitor needs to be permanently attached.
- QEMU passes the GPU to the guest via VFIO
- The guest runs
amdgpu(ornvidia) as normal - A kernel parameter forces the display connector on so Sunshine always has a display to capture
- Sunshine streams the GPU-rendered desktop over the network
- Moonlight connects from any client device
See Prerequisites for IOMMU, vfio group, and memlock setup.
Bind all devices in the GPU’s IOMMU group to vfio-pci:
vee gpu bind 0000:08:00.0
vee gpu bind 0000:08:00.1 # GPU audio — must be bound together
gpu:
mode: passthrough
pci_addr: "0000:08:00.0"
extra_vfio_addrs:
- "0000:08:00.1" # GPU audio peer — same IOMMU reset domain
rom_file: "/home/user/.vee/gpu.rom" # required for AMD Navi
anti_detect: true
ssh_user: youruser
guest_agent: true
extra_vfio_addrs passes all devices in the IOMMU group through together.
Without it QEMU cannot take ownership of the group.
For AMD Navi GPUs, rom_file is required — see AMD Navi ROM BAR Quirk.
Without a physical monitor the GPU display engine does not initialize and amdgpu reports no outputs. Force the connector on with a kernel parameter:
Edit /etc/default/grub inside the VM:
GRUB_CMDLINE_LINUX_DEFAULT="... video=DP-1:2560x1440@60e"
The e suffix forces the connector enabled regardless of hotplug detect (HPD).
Replace DP-1 with the connector your GPU uses (check ls /sys/class/drm/).
Apply and reboot:
sudo update-grub
After reboot, verify:
DISPLAY=:0 xrandr | grep connected
First boot: On the first boot after adding the parameter, plug a physical monitor into the GPU so amdgpu can initialize the display engine. Subsequent boots work headlessly via the
video=param.
Install Sunshine for your distro — see the Sunshine documentation.
Create ~/.config/sunshine/sunshine.conf:
encoder = vaapi # AMD: vaapi NVIDIA: nvenc
av1_mode = 0 # disable AV1 — deadlock on some AMD/vaapi builds
hevc_mode = 0 # disable HEVC — same issue; H.264 is stable
min_threads = 4
output_name = 0 # capture primary display
qp = 28
av1_mode / hevc_mode: Some Sunshine nightly builds have a deadlock in session teardown when AV1 or HEVC encoding is used with vaapi on AMD GPUs. Symptom:
Fatal: Hang detected! Session failed to terminate in 10 secondsfollowed by a core dump on every disconnect. Set both to0to force H.264 until a fixed release is available.
Sunshine must start after Xorg has initialized the display. Override the default service to poll xrandr until the connector is ready, then set the target resolution.
Create ~/.config/systemd/user/sunshine.service:
[Unit]
Description=Self-hosted game stream host for Moonlight
StartLimitIntervalSec=500
StartLimitBurst=5
[Service]
Environment=DISPLAY=:0
TimeoutStartSec=120
ExecStartPre=/bin/sh -c 'until xrandr 2>/dev/null | grep -q "^DisplayPort-0 connected"; do sleep 2; done'
ExecStartPre=/bin/sh -c 'xrandr --newmode "2560x1440_60.00" 312.25 2560 2752 3024 3488 1440 1443 1448 1493 -hsync +vsync 2>/dev/null; xrandr --addmode DisplayPort-0 "2560x1440_60.00" 2>/dev/null; xrandr --output DisplayPort-0 --mode "2560x1440_60.00"'
ExecStart=/usr/bin/sunshine
Restart=on-failure
RestartSec=5s
[Install]
WantedBy=xdg-desktop-autostart.target
Replace DisplayPort-0 and 2560x1440 with your connector name and resolution.
Apply:
systemctl --user daemon-reload
systemctl --user enable --now sunshine
Sunshine uses several UDP ports for the video/audio stream. On Ubuntu, UFW is
enabled by default and blocks these ports, causing Initial Ping Timeout and
session crashes. Since this is a LAN-only gaming VM, disable UFW:
sudo ufw disable
sudo apt install qemu-guest-agent
The agent is socket-activated and starts automatically when the VM is launched
with guest_agent: true in vm.yaml. No manual systemctl enable is needed.
- Open Moonlight on your client device
- Add the VM’s IP address as a host
- Enter the pairing PIN shown in Moonlight into Sunshine’s web UI at
https://<vm-ip>:47990 - Select the desktop app and connect
Sunshine’s web UI is also available at https://localhost:47990 from inside the VM for pairing and configuration.