Open-source project
musnix/musnix avatar
musnix/musnix

musnix: a NixOS module that turns a workstation into a real-time audio machine

Real-time audio in NixOS

963 stars62 forksNixBSD-3-Clause

At a glance

What is it?
musnix is a NixOS module that bundles the low level changes a Linux audio workstation needs, from PAM scheduling limits and udev rules to PREEMPT_RT kernels. The kernel story got simpler recently, because mainline 6.12 and later carries PREEMPT_RT and musnix now configures it through overrides instead of patched packages.
Who is it for?
Adopt musnix if you run NixOS for audio work and want the scheduler, udev and kernel pieces in one module instead of scattered fragments in configuration.nix. Do not expect it to configure hardware drivers for you, since ALSA Sequencer, FFADO and sound card latency handling are separate opt in options.
Can I use it commercially?
Yes. BSD-3-Clause is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 14 days ago.
What is it written in?
Mainly Nix, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.

Editorial analysis

What musnix.enable changes the moment you flip it

musnix.enable is the switch that arms everything else, and the documentation is explicit that it must be true before other base options do anything. Once set, it performs a fixed list of changes.

The CPU frequency governor is switched to `performance`. `vm.swappiness` is set to 10, which keeps the kernel from aggressively swapping audio buffers. Three udev rules are installed so that the real time clock and the high precision event timer belong to the audio group, and so that the CPU DMA latency device is owned by root and the audio group with mode 0660:

code
    KERNEL=="rtc0", GROUP="audio"
    KERNEL=="hpet", GROUP="audio"
    DEVPATH=="/devices/virtual/misc/cpu_dma_latency", OWNER="root", GROUP="audio", MODE="0660"

Four PAM limits then grant the audio group the scheduling priority and locked memory that a low latency path needs:

code
    @audio  -       memlock unlimited
    @audio  -       rtprio 99
    @audio  soft    nofile 99999
    @audio  hard    nofile 99999

Finally, the module points the plugin search environment variables at the default install locations in NixOS: `VST_PATH`, `VST3_PATH`, `LXVST_PATH`, `LADSPA_PATH`, `LV2_PATH` and `DSSI_PATH`, and permits users to install plugins under `~/.vst`, `~/.vst3`, `~/.lxvst`, `~/.ladspa`, `~/.lv2` and `~/.dssi`. This is the part that makes the module feel like an audio workstation setup rather than a kernel tweak, and it is also the part most likely to collide with a plugin manager that sets its own variables.

PREEMPT_RT without a patch on kernels 6.12 and later

The headline change in recent musnix work is about the kernel. The documentation splits the behaviour at version 6.12. For kernels 6.12 and later, PREEMPT_RT is available natively in the mainline kernel, so no external patch is required. You point musnix.kernel.packages at any mainline kernel at or above 6.12 and musnix applies the required configuration itself.

code
  musnix.kernel.packages = pkgs.linuxPackages;        # default NixOS kernel (if >= 6.12)
  musnix.kernel.packages = pkgs.linuxPackages_6_12;   # specific LTS version
  musnix.kernel.packages = pkgs.linuxPackages_latest; # latest mainline

What the module actually does to that kernel is spelled out. It overrides the configuration with `EXPERT=y` and `PREEMPT_RT=y`, and it unsets the options that are mutually exclusive with real time scheduling: `PREEMPT_VOLUNTARY`, `PREEMPT_FULL` and `RT_GROUP_SCHED`. It also sets `ignoreConfigErrors = true` so that version specific disappearing symbols, the documentation gives i915 GVT and Rust assertions as examples, do not break the build.

That last point is the interesting one. Building a kernel from nixpkgs means you inherit whatever symbol set your kernel version has, and a configuration fragment that mentions a symbol the running kernel does not have is normally a hard build failure. Turning those errors into warnings is what makes a single generic fragment work across many kernel versions. The cost is that a genuinely wrong configuration also becomes a warning rather than a failure, so a broken real time configuration can reach a machine without the build telling you.

RT patched packages for machines stuck below 6.12

Older kernels still need an externally patched build, and musnix ships those in an overlay. The documentation lists the RT patched packages as `pkgs.linuxPackages_6_1_rt`, `pkgs.linuxPackages_6_6_rt`, `pkgs.linuxPackages_6_8_rt`, `pkgs.linuxPackages_6_9_rt` and `pkgs.linuxPackages_6_11_rt`.

There are also two convenience aliases. `pkgs.linuxPackages_rt` currently resolves to `pkgs.linuxPackages_6_6_rt`, and `pkgs.linuxPackages_latest_rt` currently resolves to `pkgs.linuxPackages_6_11_rt`.

Those aliases are convenient and quietly risky. The word currently in the documentation matters: an alias that points at one version today can point at another after an overlay update, and since the alias is what most people will write in configuration.nix, the kernel on a machine can change without the configuration file changing. The fixed version packages are the safer choice if you want a reproducible machine, and the aliases are the shorter choice if you want to track the newest patched kernel the overlay provides.

One more practical note: enabling musnix.kernel.realtime rebuilds your kernel. On NixOS that means a new store path and a reboot, not an in place change, but it is still the kind of step people forget when they flip a module option and expect audio to work immediately.

Three ways to import the module, and how updates differ

The README documents three installation paths, and they differ in more than the mechanism, because they differ in how you update afterwards.

The first is a clone. You clone the project and add the path to your imports list in configuration.nix:

code
{
  imports =
    [ # ...
      /path/to/musnix/clone
    ];

  musnix.enable = true;
  users.users.johndoe.extraGroups = [ "audio" ];
}

Updates for this path are a `git pull`. The second is a channel, which is the older Nix mechanism:

code
sudo -i nix-channel --add https://github.com/musnix/musnix/archive/master.tar.gz musnix
sudo -i nix-channel --update musnix

With a channel the import becomes `<musnix>` instead of a filesystem path, and updating is `sudo -i nix-channel --update musnix`. The third is a flake, where musnix becomes an input declared as `github:musnix/musnix` next to a nixpkgs input pointed at `github:NixOS/nixpkgs/nixos-unstable`, and the NixOS module is pulled in from the flake outputs as `inputs.musnix.nixosModules.musnix` inside the `modules` list of a `nixosSystem` configuration.

The flake path is the one that fits current Nix practice, because it pins musnix in flake.lock alongside nixpkgs. The clone path gives you the shortest feedback loop, since a `git pull` changes the module immediately. The channel path is the least appealing of the three for a new setup, and the documentation offers it as an alternative rather than a recommendation.

Real-time priority comes from PAM limits, not from a group alone

It is easy to read the module as a list of conveniences and miss that two of its entries are load bearing for low latency audio.

The PAM limits are the reason a session can request the `SCHED_FIFO` scheduler at all. `rtprio 99` sets the maximum real time priority the audio group may obtain, and `memlock unlimited` removes the locked memory ceiling that would otherwise stop an audio thread from pinning its buffers. Without those two limits, a desktop session that is otherwise configured correctly still gets throttled by the resource limits of the PAM profile, and the symptom is dropouts that look like driver problems rather than policy problems.

That is also why the documentation pairs `musnix.enable = true` with a user in the audio group. The PAM rules are scoped to `@audio`, so a user outside that group gets none of the priority or memory allowance, even on a fully configured system. The example shows it as `users.users.johndoe.extraGroups = [ "audio" ]`, which is the piece people most often leave out.

The udev rules solve a parallel problem: without group ownership on the real time clock, the high precision event timer and the CPU DMA latency device, the very first client to open them takes them and other applications get permission errors. Both problems are permissions problems rather than kernel problems, which is the reason a module can fix them cleanly.

Where the module stops and the hardware work begins

musnix deliberately stops short of audio driver configuration, and the remaining options are opt in and narrow.

`musnix.alsaSeq.enable` defaults to true and loads the ALSA Sequencer kernel modules, currently only `snd_seq` and `snd_rawmidi`. `musnix.ffado.enable` defaults to false and selects the Free FireWire Audio Drivers for FireWire interfaces. `musnix.rtcqs.enable` defaults to false and installs the rtcqs command line utility, which analyses the system and suggests changes that would make it more audio friendly.

The most interesting of these is musnix.soundcardPciId, a string that holds the PCI ID of the primary sound card and is used to set the PCI latency timer. The documentation gives the command for finding it:

code
  lspci | grep -i audio

and notes explicitly that if you have a USB sound card the option is not useful. That is the clearest boundary in the whole project. The PCI latency timer is a firmware level setting on the device, and a USB interface has no equivalent knob to turn, so a large part of the modern desktop audio market is outside what this option can address.

There are more options beyond those, including a `musnix.das_watchdog.enable` switch that starts the das_watchdog service, and a family of rtirq options that install and run the rtirq script as a systemd service. Those rtirq options can be used without musnix.enable, but they do require musnix.kernel.realtime to be true, and the documentation points you at `nixos-option musnix.rtirq` and `nixos-option musnix.rtirq.enable` to read their descriptions rather than listing them inline.

A module versus hand written configuration

The alternative to musnix is writing the same settings into your own configuration.nix, which is entirely feasible. The udev rules are three lines, the PAM limits are four, the swappiness value and the frequency governor are one line each, and the plugin environment variables are a short list.

The difference in approach is where the knowledge lives. Hand written configuration puts the rules in your repository and ties them to your nixpkgs revision, so you own every line and every update decision. musnix moves that knowledge into a module that someone else maintains, and it brings the parts that are easy to get subtly wrong: the kernel configuration overrides for PREEMPT_RT, the list of options that are mutually exclusive with real time scheduling, the ignoreConfigErrors setting that makes one fragment work across kernel versions, and the set of RT patched packages for kernels older than 6.12.

The trade-off runs the other way too. Because the module is a single unit, you inherit all of it, including the parts you do not need, such as the plugin path variables and the six default install directories. And the alias packages show the cost of following a moving target: `pkgs.linuxPackages_rt` is documented as currently pointing at a specific version, so a machine that follows the alias can change kernel version without any edit on your side.

The repository layout reflects the split. The root holds flake.nix, flake.lock, default.nix, overlay.nix, release.nix, and directories for modules, pkgs, scripts, tests and a .github directory with a check workflow. The overlay is what carries the RT patched kernel packages, the modules directory carries the NixOS options, and the tests directory is what makes the difference between a module and a pile of configuration visible. The project is BSD-3-Clause licensed and publishes no GitHub releases, so there is no version tag to pin, though the last push was on 2026-10-09 and the check workflow badge in the README indicates the repository is built automatically on each change.

Editorial conclusion

Adopt musnix if you run NixOS for audio work and want the scheduler, udev and kernel pieces in one module instead of scattered fragments in configuration.nix. Do not expect it to configure hardware drivers for you, since ALSA Sequencer, FFADO and sound card latency handling are separate opt in options. Before enabling it, check that your NixOS kernel is at least 6.12 if you want mainline PREEMPT_RT, confirm the license is BSD-3-Clause, and note that the repository has no tagged releases while the last push was on 2026-10-09.

Frequently asked questions

How do I enable musnix in a NixOS configuration?

Import the module in configuration.nix, either from a clone path, a channel or a flake input, and set musnix.enable = true. The option must be true before the other musnix base options take effect.

Does musnix need a patched kernel for PREEMPT_RT?

Not on kernels 6.12 and later, where PREEMPT_RT is in the mainline kernel and musnix enables it through configuration overrides. For kernels older than 6.12 you select an RT patched package from the musnix overlay, such as pkgs.linuxPackages_6_6_rt.

Why does my user still not get real-time scheduling with musnix enabled?

The PAM limits that grant rtprio 99 and unlimited memlock apply to the @audio group, so the user must be a member of that group, for example through users.users.johndoe.extraGroups = [ "audio" ].

Can I use musnix with a USB sound card?

The module works with USB interfaces, but musnix.soundcardPciId is not useful in that case, since the option sets the PCI latency timer and USB devices have no equivalent setting.

Official sources

  1. Issues
  2. License: BSD-3-Clause
  3. musnix/musnix on GitHub
  4. README
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/musnix-musnix.svg)](https://hysenlabs.com/projects/musnix-musnix)

Was this useful?