All steps assume a fresh install of Ubuntu 25.04, GNOME 45, the NVIDIA driver 580 – GPU‑0 is head‑less, GPU‑1 is attached to the monitor. No integrated graphics inside CPU.
1. Why Wayland is different from X‑org
| Feature | X‑org (old guide) | Wayland (this guide) |
|---|---|---|
| Display server | One X server can host several “screens” (one per GPU). | Only one compositor (Mutter) runs; it can drive only one GPU directly. |
| Render‑offload | PRIME Render Offload works through the X server (__NV_PRIME_RENDER_OFFLOAD). |
Off‑load works via EGL (the same env‑var) or through the prime‑run wrapper. The compositor stays on the “primary” GPU, the heavy GL/Vulkan work is sent to the secondary GPU. |
| Vulkan | ICD filtering + env‑vars. | Same ICD filtering, but the loader will see both GPUs; we must force it to pick the head‑less one. |
| XWayland fallback | Not needed. | Many older Windows games (via Proton) still run under XWayland; they inherit the same off‑load env‑vars. |
| Wayland‑only games | N/A | Modern native‑Wayland titles (e.g., Quake II RTX, Starfield with the native Linux client) already use EGL, so the same variables apply. |
The overall goal is unchanged:
- Mutter (GNOME Shell) → GPU 1 (the card that has the monitor).
- All games/Steam titles → GPU 0 (the head‑less card) via PRIME Render Offload.
Below is a step‑by‑step manual that configures the kernel, the compositor, the environment, Vulkan, and Steam for Wayland.
2. Preliminary checks
# 2.1 Verify driver version
nvidia-smi
# Should show “Driver Version: 580.xx” and two GPUs.
# 2.2 Verify each GPU’s PCI bus ID
lspci -nnk | grep -iA2 'VGA'
# Note the BusIDs, e.g. 01:00.0 and 02:00.0 – we will need them later.
If nvidia-smi reports “NVIDIA-SMI has failed because it couldn’t communicate with the NVIDIA driver”, reinstall the driver (see Ubuntu “Additional Drivers” or download the .run package from NVIDIA).
3. Enable Wayland for the NVIDIA driver
The NVIDIA driver disables Wayland by default because the GBM backend was introduced only recently. We have to:
- Enable DRM/KMS mode‑setting for the driver.
- Tell GDM to allow Wayland.
3.1 Add kernel parameter nvidia-drm.modeset=1
sudo vim /etc/default/grub
Find the line that starts with GRUB_CMDLINE_LINUX_DEFAULT and append the parameter:
GRUB_CMDLINE_LINUX_DEFAULT="quiet splash nvidia-drm.modeset=1"
Save, then rebuild the GRUB config:
sudo update-grub
3.2 Enable Wayland in GDM
sudo vim /etc/gdm3/custom.conf
Uncomment (or add) the line:
WaylandEnable=true
Also make sure #DefaultSession=gnome-xorg.desktop is commented or removed, so the default session will be Wayland.
3.3 Reboot
sudo reboot
After reboot, confirm you are on Wayland:
echo $XDG_SESSION_TYPE
# Should print "wayland"
If you still see “x11”, log out → click the gear icon on the login screen → select “Ubuntu on Wayland”.
4. Identify which GPU will be the primary (display) GPU
In our hardware layout, GPU‑1 (BusID 02:00.0 for example) is the one with the monitor. We need to tell the NVIDIA driver that this is the “primary” GPU.
Create a modprobe configuration that forces the driver to treat GPU‑1 as the primary:
sudo vim /etc/modprobe.d/nvidia-primary.conf
Paste:
options nvidia NVreg_EnablePCIeGen3=1
options nvidia NVreg_UseThreadedInterrupts=1
options nvidia NVreg_RegisterVbios=1
options nvidia NVreg_RegistryDwords="PrimaryGPU=0x02"
# 0x02 = the second PCI function (02:00.0). Adjust if yours is different.
How to find the correct value?
The hex value is the device number part of the BusID (the middle number).
- Example:
01:00.0→ primary value0x01. - Example:
02:00.0→ primary value0x02.
Re‑load the module (or simply reboot again, which is safer):
sudo reboot
After reboot, run:
nvidia-smi -q | grep -i "GPU 0"
You should see that GPU 0 now corresponds to the display‑connected card. The head‑less card will be reported as GPU 1. The numbering can flip; what matters is the UUID – we will use the UUID later, not the index.
5. Global environment for Wayland PRIME Render Offload
Wayland sessions read environment files from /etc/environment.d/ (system‑wide) or ~/.config/environment.d/ (per‑user). We’ll create a per‑user file, because the UUID is user‑specific.
5.1 Find the UUID of the head‑less GPU
nvidia-smi -L
# Example output:
# GPU 0: NVIDIA GeForce RTX 2080 Ti (UUID: GPU-11111111-2222-3333-4444-555555555555)
# GPU 1: NVIDIA GeForce RTX 2080 Ti (UUID: GPU-aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee)
Assume GPU 1 (the second line) is the head‑less one. Copy its UUID (GPU-aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee).
5.2 Create the environment file
mkdir -p ~/.config/environment.d
vim ~/.config/environment.d/99-nvidia-prime.conf
Paste (replace GPU-aaaaaaaa‑... with your actual UUID):
# Enable NVIDIA PRIME Render Offload for Wayland
__NV_PRIME_RENDER_OFFLOAD=1
__NV_PRIME_RENDER_OFFLOAD_PROVIDER=GPU-aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee
__GLX_VENDOR_LIBRARY_NAME=nvidia
# For Vulkan – point the loader at a filtered ICD (see §6)
VK_ICD_FILENAMES=$HOME/.local/share/vulkan/icd.d/nvidia_headless_icd.json
# Optional: enable the NVIDIA Optimus Vulkan layer (helps some games)
VK_LAYER_PATH=/usr/share/vulkan/implicit_layer.d
Log out and back in (or source ~/.config/environment.d/99-nvidia-prime.conf in the current terminal) to apply the variables.
Tip: You can test that the variables are set with printenv | grep NV_PRIME and printenv | grep VK_.
6. Vulkan – force the loader to use the head‑less GPU
6.1 Copy the system ICD and edit it
mkdir -p ~/.local/share/vulkan/icd.d
cp /usr/share/vulkan/icd.d/nvidia_icd.json ~/.local/share/vulkan/icd.d/nvidia_headless_icd.json
Edit the copy:
vim ~/.local/share/vulkan/icd.d/nvidia_headless_icd.json
Add the device_id and vendor_id fields that correspond to the head‑less GPU. You can get them from nvidia-smi -q -d PCI:
nvidia-smi -q -d PCI | grep -A2 "GPU 1"
# Example output:
# Device ID : 0x1E04
# Vendor ID : 0x10DE
In case you have problem to run this command, use this one instead:
lspci -vnn | grep -i nvidiaOr the modern version of nvidia-msi:
nvidia-smi --query-gpu=gpu_name,pci.bus_id,pci.device_id,pci.sub_device_id --format=csv
Insert them (keep the existing keys, just add the new ones):
{
"file_format_version" : "1.0.0",
"ICD" : {
"library_path": "libGLX_nvidia.so.0",
"api_version" : "1.2.162",
"device_id": "0x1E04",
"vendor_id": "0x10DE"
}
}
Save and exit.
6.2 Verify Vulkan picks the right card
vulkaninfo | grep -i "deviceName"
# Should print: "NVIDIA GeForce RTX 2080 Ti (GPU-aaaaaaaa‑...)" – i.e. the head‑less GPU.
If you still see both GPUs listed, double‑check that VK_ICD_FILENAMES points exactly to the edited JSON (the variable set in §5).
7. The prime-run wrapper – a convenient way to launch Wayland games (not for latest drivers)
NVIDIA ships a small helper script called prime-run (installed with the driver). It simply exports the required env‑vars and runs the given command. It works both under Wayland (native EGL) and under XWayland (fallback for X‑only apps).
7.1 Verify it exists
which prime-run
# Should be /usr/bin/prime-run
If it is missing, install the nvidia-prime package (it provides the script):
sudo apt install nvidia-prime
Note: prime-run is intended for older NVIDIA driver setups. On newer drivers, it may not work correctly or could even be removed during driver updates.7.2 Test the wrapper with a simple GL program
prime-run glxgears -info
# You should see the FPS counter and, in another terminal,
# watch -n1 nvidia-smi
# The `glxgears` process will appear under the head‑less GPU’s UUID.
If you get a black window or “failed to create EGL context”, make sure the Wayland session variables are active (re‑log in) and that the kernel param nvidia-drm.modeset=1 is present (cat /proc/cmdline).
8. Configure GNOME Mutter to stay on the display GPU
Mutter (the Wayland compositor) automatically uses the primary NVIDIA GPU (the one we forced in §4). No extra configuration is required, but you can tune a few settings to improve smoothness:
gsettings set org.gnome.mutter experimental-features "['variable-refresh-rate']"
gsettings set org.gnome.mutter enable-animations true
# Enable “Sync to VBlank” (helps when the compositor mixes frames)
gsettings set org.gnome.mutter sync-to-vblank true
These settings are stored in dconf and survive reboots.
8. Gaming‑specific NVIDIA tweaks (optional but recommended)
Even though the head‑less GPU never drives a monitor, you still want it to stay cool and deliver full performance.
8.1 Enable “Coolbits” for manual fan control
Edit /etc/modprobe.d/nvidia-coolbits.conf:
sudo nano /etc/modprobe.d/nvidia-coolbits.conf
options nvidia NVreg_Coolbits=28
Re‑load the driver (or reboot). Then, after logging back into Wayland:
prime-run nvidia-settings -a '[gpu:1]/GPUFanControlState=1' # GPU‑1 is the head‑less one now
prime-run nvidia-settings -a '[fan:0]/GPUTargetFanSpeed=85'
NOTE:prime-runis required because thenvidia-settingsUI talks to the currently active GPU (the display GPU). By prefixing the command withprime-run, you force it to address the head‑less card.
8.2 Set performance level (PowerMizer)
prime-run nvidia-settings -a '[gpu:1]/GPUPowerMizerMode=1' # Prefer maximum performance
prime-run nvidia-settings -a '[gpu:1]/GPUPerfMode=0' # “Adaptive” mode off
You can persist those settings by adding them to ~/.nvidia-settings-rc (the file is read by nvidia-settings at startup).
8.3 Reduce PCIe latency (advanced)
Add another kernel option (optional, only if you notice “low GPU utilization” in nvidia-smi while a game is running):
sudo nano /etc/default/grub
# Append to the same line as before:
GRUB_CMDLINE_LINUX_DEFAULT="quiet splash nvidia-drm.modeset=1 nvidia.NVreg_RegistryDwords=PowerMizerEnable=0x1;PerfLevelSrc=0x2222"
sudo update-grub
sudo reboot
9. Launching games under Wayland
9.1 Native‑Wayland (EGL) games
Many recent titles ship a native Wayland client (e.g., Quake II RTX, Doom Eternal with the Linux client). They already use EGL, so you only need the environment variables we set in §5.
Run the game normally from the GNOME menu or via Steam – the compositor will stay on GPU 1, and the heavy rendering should be dispatched to GPU 0 automatically.
9.2 XWayland fallback (most Proton/Windows games)
Proton launches the Windows executable under Wine → XWayland. XWayland is just another X server that runs inside the Wayland session. Because we exported the PRIME variables globally, every XWayland client inherits them.
No extra work is required; just make sure the variables are present (they are, thanks to the 99-nvidia-prime.conf file).
9.3 Using the prime-run wrapper manually
If you ever need to launch a non‑Steam program (e.g., a custom demo, vkcube, or a sandboxed Wine app) you can explicitly call:
prime-run <command>
Examples:
prime-run glxgears -info # OpenGL test
prime-run vkmark_vulkan # Vulkan benchmark (if installed)
prime-run ./my_custom_game.sh # Any script or binary
prime-run simply does:
env __NV_PRIME_RENDER_OFFLOAD=1 \
__NV_PRIME_RENDER_OFFLOAD_PROVIDER=$GPU_UUID \
__GLX_VENDOR_LIBRARY_NAME=nvidia \
"$@"
10. Configuring Steam for Wayland
Steam itself runs as a Wayland client (since Ubuntu 25.04 ships Steam 1.0.0‑beta with Wayland support). The global launch options we add will be inherited by every game, native or XWayland.
10.1 Set global Steam launch options
Open Steam → Settings → General → Enable the Steam Play compatibility tools (if you use Proton).
Now add the three environment variables before the %command% placeholder:
__NV_PRIME_RENDER_OFFLOAD=1 __GLX_VENDOR_LIBRARY_NAME=nvidia %command%
Why only those two?
__NV_PRIME_RENDER_OFFLOAD=1tells the NVIDIA driver to forward all GL/EGL calls to the secondary GPU.__GLX_VENDOR_LIBRARY_NAME=nvidiaforces the OpenGL library to be the NVIDIA one (important for XWayland games).
The Vulkan variables (VK_ICD_FILENAMES etc.) are already present globally in the user environment, so we do not need to repeat them here.
10.2 Per‑game launch options (optional)
If a specific title misbehaves, you can override the global variables just for that game:
- Right‑click the game → Properties → General → Launch Options.
- Click OK and launch the game.
Enter (replace GPU-aaaaaaaa… with your head‑less UUID if you want to be explicit):
__NV_PRIME_RENDER_OFFLOAD=1 __NV_PRIME_RENDER_OFFLOAD_PROVIDER=GPU-aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee %command%
10.3 Verify the game runs on the head‑less GPU
Open a terminal inside the game’s runtime (Steam → right‑click the game → “Show in folder”, then run ./game_executable from a terminal). While it runs, watch the driver:
watch -n 1 nvidia-smi
You should see the process listed under the head‑less GPU’s UUID, with memory usage matching the game (e.g., 2–4 GB). If it still appears under the display GPU, double‑check:
- The kernel parameter (
nvidia-drm.modeset=1) is active (cat /proc/cmdline). - The environment variables are present (
printenv | grep NV_PRIME). - The UUID in
__NV_PRIME_RENDER_OFFLOAD_PROVIDERmatches the head‑less card exactly.
11. Performance testing
11.1 OpenGL (EGL) test
prime-run glxgears -info
# You should see a smooth ~200‑250 FPS counter, similar to native X‑org off‑load.
11.2 Vulkan benchmark
Install the Vulkan demo vkcube (works under Wayland directly):
sudo apt install vulkan-tools
prime-run vkcube --fullscreen
While it runs, monitor the GPUs:
watch -n 1 nvidia-smi
The head‑less GPU should be at high utilization (≈90 % GPU usage), while the display GPU stays idle (<5 %).
11.3 Real‑world game
Pick a Vulkan‑enabled title (e.g., DOOM Eternal). Ensure its Steam launch options contain the off‑load variables (see §8). Launch, then:
watch -n 1 nvidia-smi
You should see the game process attached to the head‑less GPU’s UUID.
If the FPS is close to the native‑single‑GPU value (within 5‑10 % loss), the setup works perfectly.
12. Optional: Keep a headless X server for XWayland fallback
Even on Wayland, some games still fall back to XWayland. Having a dedicated X server for the head‑less GPU can make XWayland faster because it avoids extra “wayland‑to‑xwayland” round‑trips.
Create a systemd service that starts an X server bound to the head‑less card (GPU 0). The X server will be invisible (no monitor) and only used by XWayland.
sudo vim /etc/systemd/system/headless-x.service
[Unit]
Description=Headless X server (NVIDIA GPU0) for XWayland offload
After=display-manager.service
Wants=display-manager.service
[Service]
User=YOUR_USERNAME # ← replace
Environment=DISPLAY=:0
ExecStart=/usr/bin/X :1 -config /etc/X11/xorg.conf.d/headless.conf
Restart=always
RestartSec=5
[Install]
WantedBy=graphical.target
Create a minimal Xorg config that only loads the head‑less GPU (no monitor):
sudo mkdir -p /etc/X11/xorg.conf.d
sudo vim /etc/X11/xorg.conf.d/headless.conf
Section "Device"
Identifier "NvidiaHeadless"
Driver "nvidia"
BusID "PCI:01:00:0" # <-- use the BusID of the head‑less card
Option "Coolbits" "28"
EndSection
Section "Screen"
Identifier "Screen0"
Device "NvidiaHeadless"
EndSection
Enable the service:
sudo systemctl daemon-reload
sudo systemctl enable --now headless-x.service
Now XWayland clients (most Proton games) will be able to reach the head‑less GPU through the same PRIME env‑vars we already exported.
You can skip this whole X‑server section if all your games support native Wayland/EGL (most modern titles do). It is only a safety net for legacy titles.
13. Troubleshooting checklist (Wayland)
| Symptom | Likely cause | How to fix |
|---|---|---|
| Session still starts on X11 | WaylandEnable=true not applied or GDM still forces Xorg. |
Re‑log out → click the gear → pick “Ubuntu on Wayland”. Verify /etc/gdm3/custom.conf line is uncommented and nvidia-drm.modeset=1 is in the kernel cmdline (cat /proc/cmdline). |
| Desktop compositor flickers or freezes | Primary GPU not set correctly (wrong PrimaryGPU= value). |
Re‑check the hex value in /etc/modprobe.d/nvidia-primary.conf. Reboot after any change. |
prime-run prints “Unable to open DRM device” |
nvidia-drm.modeset=1 missing or driver not loaded with KMS. |
Verify cat /sys/module/nvidia_drm/parameters/modeset → should be Y. If not, redo §3.1 and reboot. |
| GL games (XWayland) still use the monitor GPU | Off‑load env‑vars not visible to XWayland. | Ensure VK_ICD_FILENAMES and __NV_PRIME_RENDER_OFFLOAD are exported system‑wide (e.g., add them to /etc/environment and to ~/.config/environment.d/). Then restart XWayland: killall Xwayland && systemctl --user restart gnome-shell. |
| Vulkan loader reports “no compatible ICD found” | The filtered ICD JSON path is wrong or the file is malformed. | Run cat $VK_ICD_FILENAMES → must print the full path to the JSON. Validate JSON with jq . $VK_ICD_FILENAMES. |
| Game crashes with “Failed to create EGL context” | NVIDIA Wayland support not compiled (GBM backend missing). | Make sure you installed the nvidia-driver-580 package from the Ubuntu repositories (it ships with the GBM backend). If you used the .run installer, reinstall using the Ubuntu Additional Drivers UI. |
| High temperatures on the head‑less GPU while idle | Fan curve not set or Coolbits missing. |
Add Option "Coolbits" "28" to the head‑less GPU’s Device stanza in /etc/modprobe.d/nvidia-primary.conf (or a separate file) and reboot. Then run prime-run nvidia-settings and set a manual fan curve. |
nvidia-smi shows 0 % utilization for the game |
Off‑load not active (variables missing). | Open a terminal inside the game (Steam → “Open in Terminal” if available) and run `printenv |
14. One‑page “quick‑start” summary
# 1. Enable DRM KMS for NVIDIA
sudo sed -i 's/^GRUB_CMDLINE_LINUX_DEFAULT="/&nvidia-drm.modeset=1 /' /etc/default/grub
sudo update-grub
# 2. Enable Wayland in GDM
sudo sed -i 's/^#WaylandEnable=false/WaylandEnable=true/' /etc/gdm3/custom.conf
# 3. Reboot and verify Wayland
reboot
echo $XDG_SESSION_TYPE # → wayland
# 4. Set primary (display) GPU (adjust 0x02 to your BusID)
sudo tee /etc/modprobe.d/nvidia-primary.conf <<EOF
options nvidia NVreg_EnablePCIeGen3=1
options nvidia NVreg_UseThreadedInterrupts=1
options nvidia NVreg_RegisterVbios=1
options nvidia NVreg_RegistryDwords="PrimaryGPU=0x02"
EOF
sudo reboot
# 5. Get UUID of head‑less GPU
HEADLESS_UUID=$(nvidia-smi -L | awk 'NR==2{print $NF}' | tr -d '()')
echo $HEADLESS_UUID # copy this string
# 6. Create per‑user environment
mkdir -p ~/.config/environment.d
cat > ~/.config/environment.d/99-nvidia-prime.conf <<EOF
__NV_PRIME_RENDER_OFFLOAD=1
__NV_PRIME_RENDER_OFFLOAD_PROVIDER=$HEADLESS_UUID
__GLX_VENDOR_LIBRARY_NAME=nvidia
VK_ICD_FILENAMES=$HOME/.local/share/vulkan/icd.d/nvidia_headless_icd.json
EOF
logout # then log back in
# 7. Filter Vulkan ICD
mkdir -p ~/.local/share/vulkan/icd.d
cp /usr/share/vulkan/icd.d/nvidia_icd.json ~/.local/share/vulkan/icd.d/nvidia_headless_icd.json
# edit the copy, add device_id/vendor_id for the head‑less card (see nvidia-smi -q -d PCI)
# 8. Test OpenGL & Vulkan
prime-run glxgears -info # should show head‑less GPU
VK_ICD_FILENAMES=$HOME/.local/share/vulkan/icd.d/nvidia_headless_icd.json vulkaninfo | grep deviceName
# 9. Configure Steam (global launch options)
# Settings → General → “Enable the Steam Play compatibility tools”
# Add to “Launch options (global)”
# __NV_PRIME_RENDER_OFFLOAD=1 __GLX_VENDOR_LIBRARY_NAME=nvidia %command%
# (Vulkan variables are already in the environment.)
# 10. Play!
prime-run steam &
# or just launch a game from the normal Steam UI.
If every test shows the head‑less GPU being used (nvidia-smi shows the game’s PID under that UUID), your Wayland dual‑GPU workstation is ready.
Enjoy buttery‑smooth frames, full RTX effects, and a responsive GNOME desktop—all while staying on the modern, secure Wayland stack! 🎮✨