After using NixOS for the last 5+ years as my main OS, I
end up with a configuration
with many things that are interesting for one reason or another, but it is not
listed anywhere (well, except if you are the kind of person that reads man 5 configuration.nix
or the release notes in every release).
So kind in the same spirit as my list of things that I miss in Go, here is a list of modules that I find neat in NixOS and are not default already. Again, the list is unordered since this makes it easier to update in the future if I find something else, but also I don't want to think too hard about an order here.
With all above, let's start.
networking.nftables
nftables is, accordingly to Wikipedia:
nftables is a subsystem of the Linux kernel providing filtering and classification of network packets/datagrams/frames.
It is basically a replacement of the venerable
iptables, that still exist and is the
default program to configure the famous networking.firewall
, the declarative
Firewall that NixOS enable by default.
To enable, it is simple, just add to your configuration:
{
networking.nftables.enable = true;
}
And thanks to the fact that NixOS's Firewall is declarative, everything should
still work as expect: any service that you set openFirewall = true
will still
have its ports open, if you set networking.firewall.allowPing = false
it will
still disable pings like before, etc.
If you look at the documentation of the above option, you will find the following warning:
Note that if you have Docker enabled you will not be able to use nftables without intervention. Docker uses iptables internally to setup NAT for containers. This module disables the ip_tables kernel module, however Docker automatically loads the module. Please see https://github.com/NixOS/nixpkgs/issues/24318#issuecomment-289216273 for more information.
I don't use Docker (switched to Podman instead for quite a long time), so I
don't know how bad the situation is. Also keep in mind that nftables
does
offer iptables-compat
for compatibility with old iptables scripts, so it is
most likely Docker doing something weird here.
Now, the actual advantage from the user here is not clear: the main advantage
from my point of view (and the reason I used to use in other distros like Arch)
is the improved syntax, however if you are using the declarative NixOS's
Firewall you are not interacting with either iptables
or nftables
directly
anyway. nftables
is supposed to be more efficient, but not sure most users
will care about this.
However if you are the kind of person that needs custom rules, switching to
nftables
does bring a few benefits, including
networking.nftables.checkRuleset
(enabled by default), that checks if your
ruleset has syntax errors during build time. Really valuable to avoid issues
only after switch.
Anyway, this is one of those options that I think it should be the default for
a long time, since most of the new development in NixOS firewall seems to be
focusing nftables
for a while.
system.switch.enableNg
This one I just discovered
today, but it has been available for a while (~2 months if you're using
nixos-unstable
). Finally someone is rewriting
switch-to-configuration.pl
,
the Perl script that is called everytime you run nixos-rebuild switch
.
Now, I am not one of those "rewrite in Rust" zealots, but in this case this is
definitely worth it: switch-to-configuration.pl
is one of those pieces of
code in NixOS that most people avoid touching at the fear of breaking
something. There is a reason why
nixos-rebuild
is as convoluted as it is, because even if it is a messy shell script, most
people preferred to workaround issues from the switch-to-configuration.pl
inside it than trying to understand the mess that switch-to-configuration.pl
is.
Trying this one is easy:
{
system.switch = {
enable = false;
enableNg = true;
};
}
Yes, you need to explicit set system.switch.enable = false
, since the default
is true
.
By the way, what is the reason you would want to set system.switch.enable = false
before the enableNg
appeared you ask? For systems that are immutable
and updated by e.g.: image upgrades instead of modifying root.
Enabling switch-to-configuration-ng
right now is mostly for testing purposes,
but one of the advantages that I saw is that system switches are (slightly)
faster:
$ hyperfine "sudo nixos-rebuild switch" # switch-to-configuration.pl Benchmark 1: sudo nixos-rebuild switch Time (mean ± σ): 3.576 s ± 0.035 s [User: 0.004 s, System: 0.014 s] Range (min … max): 3.522 s … 3.645 s 10 runs
$ hyperfine "sudo nixos-rebuild switch" # switch-to-configuration-ng Benchmark 1: sudo nixos-rebuild switch Time (mean ± σ): 3.394 s ± 0.080 s [User: 0.004 s, System: 0.013 s] Range (min … max): 3.325 s … 3.608 s 10 runs
But yes, the difference is not enough to make a significant impact, and it is
not the objective anyway. The real reason for the rewrite is to make it easier
to collaborate. I hope one day we also have someone brave enough to rewrite the
nixos-rebuild
script in something saner.
boot.initrd.systemd
A quick recap on how a modern Linux distro generally boots: the first thing
that the bootloader (say GRUB or
systemd-boot) loads is initrd
(initial
ramdisk), a small image that runs from RAM and includes the Linux kernel and
some utilities that are responsible for setting up the main system. For
example, one of the responsibilities of the initrd
is to mount the disks and
start init system (systemd
).
It may surprising that this initrd
image does not generally include
systemd
. Traditionally initrd
is composed by a bunch of shell scripts and a
minimal runtime (e.g.: busybox), however systemd
can also do this job since a long time ago. It is just the role of the distros
to integrate systemd
inside the initrd
.
This is what boot.initrd.systemd
does: enable systemd
inside the initrd
.
It make a few subtle changes:
- If you are using Full Disk Encryption via LUKS, you will get a different password prompt at login
- You will get
initrd
time information if usingsystemd-analyze
to measure boot time- You can get even more information (bootloader) if you also use
systemd-boot
- You can get even more information (bootloader) if you also use
- You will also get
systemd
style status about services duringinitrd
(not only afterwards)
But I think the main reason is that since systemd
is event-driven, it should
make boot more reliable, especially in challenging situations (like booting
from network). I can't say that I have any system like this to test if it is
actually more reliable or not, but I don't remember having any issues since I
set boot.initrd.systemd.enable = true
, so there is that.
services.pipewire
If there is something in that list that has a good chance that you're using already, it is this one, especially if you're using Wayland. Still, I think it is interesting to include in this list since PipeWire is great.
The experience with PipeWire until now for me was seamless: I never had any issues with it, all my applications still work exactly as it always worked. I also didn't had any issues with PulseAudio for a while, but I still remember when I first tried PulseAudio during the 0.x in Fedora and having tons of issues. So bonus points for PipeWire developers for polishing the experience of enough that most people will feel no difference.
To enable PipeWire, I would recommend:
{
services.pipewire = {
enable = true;
alsa.enable = true;
pulse.enable = true;
# jack.enable = true;
};
security.rtkit.enable = true;
}
This enables both ALSA and PulseAudio emulation support in PipeWire for maximum
compatibility with desktop applications (you can also enable
jack
if you use professional audio applications).
It also enables rtkit
, allowing PipeWire
to get (soft) realtime, helping avoiding cracks during high CPU load.
I also recommend taking a look at the Wiki article, that has multiple interesting configurations that can be added for low-latency setups or improved codecs for Bluetooth devices.
networking.networkmanager.wifi.backend = "iwd"
There is a good change that you're using
NetworkManager
to manage network,
especially for Wi-Fi. And if that is the case, I can't recommend enough
changing the backend from the default wpa_supplicant
to
iwd
.
If you think that your Wi-Fi takes a long time to connect/re-connect, it may be
because wpa_supplicant
. iwd
seems much more optimised in this regard, and
since switching to it I never felt that my Wi-Fi was worse than other OSes (and
generally slightly better than Windows, but keep in mind that this is a
non-scientific comparison).
Not saying that I never had Wi-Fi issues since switching to iwd
, however
switching back to wpa_supplicant
in those cases never fixed the issue (it was
the same or worse), so I assume either bad hardware or drivers in those cases.
boot.tmp.useTmpfs
Added in 2024-08-22
Most Linux distro nowadays mount /tmp
inside
tmpfs, storing the files inside RAM (and
making them actually temporary, e.g.: does not persist between reboots). There
is a long discussion whether this makes
sense or not, but it is a fact that using /tmp
as an storage for small files
generally makes sense.
However, NixOS still stores /tmp
inside /
by default. The main reason for
this is because Nix, by default, still builds everything in TMPDIR
(that
defaults to /tmp
if not set). To fix the situation you can do:
{
boot.tmp.useTmpfs = true;
systemd.services.nix-daemon = {
environment.TMPDIR = "/var/tmp";
};
}
This ensures that Nix will build in /var/tmp
, that is the temporary directory
for large files.
If you don't want to use /tmp
inside tmpfs for some reason, I recommend at
least setting boot.tmp.cleanOnBoot = true
. This is to avoid issues with e.g.:
mistakenly writing a program that depends in /tmp
being persistent, because
this is the behavior that most other distros expects.
zramSwap
Added in 2024-08-22
Have a system with small amounts of RAM? You can download more RAM for free,
just click this link. Well, just kidding, but
you can actually improve RAM usage by compressing it. This is what
zram
does: it will create a block
device using part of your RAM that will be compressed.
While this means that whatever is in the compressed part can't be used as-is (it needs to be decompressed first), it means you can store a lot more information. And since RAM is much faster than disk, this generally is a good trade-off. It is still recommended that you have some swap space though, e.g.: to swap pages that are unused.
To enable it, you can do:
{
zramSwap = {
enable = true;
algorithm = "zstd";
};
}
You can play a little with the zramSwap.algorithm
parameter, but I recommend
either lz4
or zstd
. It is important for the compression algorithm to be
fast, since you probably want to minimise CPU usage. Well, unless you have a
very good CPU and small amounts of RAM, them it may make sense to use an
algorithm that compress better but uses more CPU.
services.fstrim
Added in 2024-08-22
Do you have a SSD? If so, you probably want to trim it regularly to keep a good performance. This should be as easy as:
{
services.fstrim.enable = true;
}
One caveat though: if you are using Full Disk Encryption with LUKS, you also
need to enable boot.initrd.luks.devices.<name>.allowDiscards
, otherwise
fstrim
will not work. The reason that this is not enabled by default is
because there are some security
implications.
It shouldn't be an issue for most people, but if you are paranoid please assess
your risks first.
boot.binfmt.emulatedSystems
Added in 2024-08-22
This is one of my favorites. Do you want to have the power to run binaries from other architectures like ARM or RISC-V? In NixOS it is easy:
{
boot.binfmt.emulatedSystems = [ "aarch64-linux" "riscv64-linux" ];
}
This will install a QEMU emulator for the target architectures, and configure
binfmt
so that the kernel will
automatically detect when a binary from another architecture is run,
running it with the QEMU emulator instead.
Now, keep in mind that you still need e.g.: libraries for the target architecture. However this is where the Nix magic comes in, you can easily compile something to another architecture as:
$ uname -a
Linux sankyuu-nixos 6.10.3 #1-NixOS SMP PREEMPT_DYNAMIC Sat Aug 3 07:01:09 UTC 2024 x86_64 GNU/Linux
$ cd nixpkgs
$ nix build .#hello --system aarch64-linux
$ file ./result/bin/hello
./result/bin/hello: ELF 64-bit LSB executable, ARM aarch64, version 1 (SYSV), dynamically linked, interpreter /nix/store/kz7xglxzhad64v667wwpn8vrxhjwcbna-glibc-2.39-52/lib/ld-linux-aarch64.so.1, for GNU/Linux 3.10.0, not stripped
$ ./result/bin/hello
Hello, world!
And since the binary will be linked with the libraries from the target architecture, everything works as expect.
You also need to temper your expectations: QEMU emulation is slow. It is sufficient fast to build small programs, but if you need to build something bigger, I would recommend you to do it via cross-compilation instead.
services.dbus.implementation = "broker"
Added in 2024-08-22
Someone in Lobste.rs remembered me of this gem: dbus-broker, a high performance implementation of D-Bus.
For those who doesn't know, D-Bus is the Inter-Process Communication protocol used mostly in Linux desktop. And distros like Arch and Fedora already use it to default, so there aren't lots of reason to not use it.
services.irqbalance
Added in 2024-08-23
Here is one that I have no experience but got curious after this comment in Lobte.rs:
One more option to add is maybe
services.irqbalance.enable
. For my old laptop it’s a life savior: no more freezes of the DE or video playback during heavy operations like e.g. compilation.
Never tried it before but will give a try!