I switched to Linux on all of my machines a few years ago, but the announcement of the Steam Frame got me interested in playing VR again. This page details what I needed to get things working. Importantly, I'm also going to include the specific reason for each setting/software used, so future readers can identify if it's still needed.
I don't intend for this page to be the ultimate resource for VR on Linux, you should refer to the Linux VR Adventures Wiki for that. Many of the fixes I had to use were already documented there.
General Setup
I have Steam installed via programs.steam.enable, but I have a seperate user account for video games (that is not an admin account). I use KDE 6 on Wayland, with an RTX 2080 with the open nvidia drivers. I also don't use Home Manager, so there are a few workarounds.
My headset is a Valve Index with the Knuckles controllers, but the V1 base stations (the ones that came with the HTC Vive).
SteamVR
I started with simply installing SteamVR without any additional configurations outside of programs.steam.enable. To my surprise, this mostly worked (with caveats detailed below). There was nothing weird required for room setup or connections, however I had used my headset + controllers on Windows prior, so I didn't have to do any connection tinkering. I was able to play lighter games like Beat Saber without issue, and H3VR worked well.
I would've preferred to stick with SteamVR, but asynchronous reprojection does not work. When you start SteamVR, it tells you that you need admin access to finish setup. This is needed to set the capabilities of the SteamVR compositor binary. However, because NixOS packages Steam under bwrap, it will never succeed. You can manually enter the setcap command to make SteamVR think that it is properly set up, but bubblewrap will still override the executable capabilities at runtime. The NixOS wiki suggests removing this functionality from bubblewrap with a patch, but I personally wasn't comfortable with the security implementations of doing that since it would apply to everything on Steam, and it seemed potentially unreliable.
I spent some time playing around with the idea of a program to launch the compositor outside the bwrap container through IPC, but after a while I realized that it would be way too much work (and possibly impossible).
While I didn't notice the lack of asynchronous reprojection on Beat Saber or H3VR, I absolutely noticed it on VRChat. VRChat is infamously poorly optimized, and it has its own section below, but it was clear that SteamVR on NixOS was not an option for VRChat.
As a side note, I noticed a lot of failed calls to lsusb by SteamVR in its logs, so I added usbutils to programs.steam.extraPackages. This did not appear to actually fix anything, as the calls still failed, but the failed calls did not appear to cause any problems anyhow.
Monado
I was considering abandoning NixOS for gaming when it was suggested I use Monado instead of SteamVR, which apparently had better performace than SteamVR in general.
Monado can by adding the following to your configuration.nix (this is also on the NixOS wiki):
services.monado = {
enable = true;
# sets cap_sys_nice which is needed for reprojection. This is the default,
# but I have it explicitly highlighted. I read somewhere that doing so was
# not enough, and that you needed to use renice on the running process, but
# I did not find that to be needed.
highPriority = true;
# sets monado as the default OpenXR runtime
defaultRuntime = true;
# overwrites the runtime whenever monado is started. Needed because SteamVR
# likes to set itself as the default runtime (we still need to use SteamVR
# occasionally, so this is helpful)
forceDefaultRuntime = true;
};
systemd.user.services.monado.environment = {
# Uses SteamVR's lighthouse tracking - I believe this means SteamVR must be
# installed and configured, but I have not verified it. I think there are
# FOSS alternatives, but I have not looked into them. This setting caused
# issues detailed below.
STEAMVR_LH_ENABLE = "1";
# Use the compute pipeline for rendering. I cannot find documentation for
# this option, so I'm not 100% sure what it does. It seems like it's been
# made the default in 25.1, so this can be removed soon.
# https://gitlab.freedesktop.org/monado/monado/-/blob/stable-25.1/src/xrt/compositor/main/comp_settings.c#L16
XRT_COMPOSITOR_COMPUTE = "1";
};
Enabling STEAMVR_LH_ENABLE caused monado to segfault whenever it was started due to using a SteamVR API incorrectly (it was changed in recent releases), so I switched to using the latest git version. However, I noticed that just a few days before writing this, monado was updated to 25.1 (on 2025-12-09), which means that that can be removed when nixpkgs-unstable is updated.
Note that at time of writing, Monado doesn't support hotplugging of devices. This means that you have to ensure that controllers, base stations, and headset are plugged in and connected before starting Monado. In some cases (e.g. the headset's DP cable isn't plugged in), it will just segfault on launch.
To start Monado, use:
systemctl --user start monado.service
By default, it's set up to autostart when something tries to connect to it, so I rarely start it manually.
Since it's a systemd service, you can view its logs with:
journalctl --user --follow -p7
I like seeing all the other logs as well, so I don't limit journalctl to just Monado, which can be done with by adding --unit monado.service.
I've also found that Monado doesn't respond to normal attempts to stop it. To stop it, I've had to send SIGKILL.
systemctl --user kill monado.service --signal 9
Allowing Steam to use Monado requires adding the ipc path in the environment:
programs.steam = {
package = pkgs.steam.override {
extraProfile = ''
# Expose Monado to steam runtimes
export PRESSURE_VESSEL_FILESYSTEMS_RW=$XDG_RUNTIME_DIR/monado_comp_ipc
# Prefix the launch options for openvr-only games with:
# VR_OVERRIDE=$OPENCOMPOSITE_PATH
export OPENCOMPOSITE_PATH="${pkgs.opencomposite}/lib/opencomposite"
'';
};
};
For OpenXR games running in Proton, you need to add: PRESSURE_VESSEL_IMPORT_OPENXR_1_RUNTIMES=1
I also had to add nvidia-modeset.conceal_vrr_caps=1 as a kernel parameter to fix the severe screen tearing.
If Audio doesn't work right away, it's because audio is handled by modifying the GPU's output profiles rather than adding a new output device. For me, this meant having to select different profiles for the TU104 HD Audio Controller device in KDE's sound settings. It doesn't appear to be consistent which profile corresponds to the headset, so I occasionally need to go through the profiles and test each one until I find the right one.
WLXOverlay-S
WLXOverlay-S is awesome. It has acted as my replacement for OVR Toolkit.
You should look at the github README for an introduction, but it is high-quality software. It includes A VR Wayland compositor, which is I think is just about the coolest thing ever.
My setup for it involved modifying the example watch config to add a view for nvtop. It also wants to use pactl for volume control by default. This is most likely not wanted since modern systems use pipewire. I haven't changed it myself to use wpctl, but I will at some point.
VRChat
VRChat was and is the most challenging VR game to get working, mostly because of its horrible performance (I wonder if they would be interested in hiring me as a performance engineer).
As I mentioned above, SteamVR was completely unworkable for me. Right now, I am using the following launch options that I've found to give the best experience. Some I copied from posts on the ProtonDB page without testing them specifically. These are labeled as such. VRChat is particularly difficult to measure the performance of, so it's difficult to know which settings have an impact.
Launch environment variables:
PROTON_MEDIA_USE_GST=1- use winegstreamer as the video backend. This was apparently an issue, but I haven't tested without it.PROTON_USE_NTSYNC=1 PROTON_NO_ESYNC=1 PROTON_NO_FSYNC=1- use ntsync synchronization. I don't know if theNO_ESYNCandNO_FSYNCis necessary. Expanded on below.PROTON_ENABLE_NVAPI=1 PROTON_HIDE_NVIDIA_GPU=0- I have them set, but they've been redundent since Proton 9- I don't have the import openxr runtimes var set, but it appears to be working just fine. I will have to test it later.
Launch arguments (comes after %command%):
--enable-hw-video-decoding- forces hardware video encoding. I don't think this does anything on my Nvidia GPU, but according to the documentation it's disabled by default on AMD. As I will be switching to AMD soon, I'm keeping this.--enable-avpro-in-proton- needed for some video players. The default of whether it's enabled or not keeps changing, so I'm keeping it on for now.screen-width 640 -screen-height 480 -screen-fullscreen 1- only affects desktop display. I expect this improves performance, but I haven't measured it.
I found using ntsync to be absolutely essential for performance. I have not specifically tested the other synchronization methods, so I don't know which one is the default on my system. I initially noticed high CPU usage from wineserver, and when I enabled WINEDEBUG I got gigabytes per second of log output that appeared to be ipc. The specific symptoms were strong indicators of synchronization slowness that are too detailed to write here. Adding the ntsync kernel module fixed that (and as a bonus fixed the audio issues that were present as well).
Hot Dogs, Horseshoes & Hand Grenades
Hot Dogs, Horseshoes & Hand Grenades (known as H3VR) generally works well under Monado. It has issues with headset positioning (obvious with night vision) that are fixed with OXR_PARALLEL_VIEWS=1.
Other resources
As I stated in the introduction, a lot of this article is restating what was already in the Linux VR Adventures Wiki, as I found they were needed the hard way.
Other than that, here are some other resources I came acroess in my Linux VR Adventures:
- NixOS wiki VR page
- Xe Iaso's My VR Hell on NixOS - a little outdated, more of a cry for help than anything else, and more evidence that when you get good enough at touching computers everything gets harder and nothing works.