Enabling io_uring for the MooseFS Client on Ubuntu 26.04
This guide shows how to run the MooseFS Community Edition client (mfsmount) with
FUSE‑over‑io_uring on a stock Ubuntu 26.04 ("Resolute Raccoon") machine.
io_uring lets the kernel and the MooseFS client exchange FUSE requests through a shared
ring buffer instead of the traditional read()/write() calls on /dev/fuse. That means
fewer system calls and context switches per operation, which can noticeably improve
throughput and latency on busy mounts. mfsmount detects at startup whether the FUSE
library it is linked against supports io_uring; when it does, you can turn it on with
-o io_uring.
The short version
There are two independent things you need, and Ubuntu 26.04 only gives you one of them out of the box:
-
A kernel that supports FUSE‑over‑io_uring, with the feature switched on. Good news: the stock Ubuntu 26.04 kernel (Linux 7.0) is already built with
CONFIG_FUSE_IO_URING=y. You do not need to compile a custom kernel. You only have to flip a runtime switch:enable_uring. -
A FUSE userspace library new enough to actually work. This is the catch. The
libfuse3package shipped in Ubuntu 26.04 is 3.18.2, which was the first release series to include io_uring — and it does not work correctly for this use case. The fixes that make FUSE‑over‑io_uring actually function landed after 3.18.2, on the way to 3.19. So you need to build libfuse from the current upstream sources and havemfsmountuse that build.
Everything below is just those two points, done carefully.
Before you start
This guide assumes:
- A clean Ubuntu 26.04 LTS install on
amd64, withsudoaccess. - A MooseFS master already running somewhere on your network that this client will
connect to. We'll use the hostname
mfsmasterin the examples — replace it with your master's hostname or IP. - You are comfortable pasting shell commands into a terminal.
A quick reality check that you're on a kernel with the feature compiled in:
grep CONFIG_FUSE_IO_URING "/boot/config-$(uname -r)"
You want to see CONFIG_FUSE_IO_URING=y. If instead you get # CONFIG_FUSE_IO_URING is not set or no output at all, your kernel can't do this and the rest of the guide won't
help until you boot a kernel that has it.
Step 1 — Turn on the kernel's enable_uring switch
Even though the capability is compiled in, the kernel keeps it off by default. Turn it on:
echo 1 | sudo tee /sys/module/fuse/parameters/enable_uring
Confirm it took:
cat /sys/module/fuse/parameters/enable_uring
# expect: Y
This setting is not persistent — it resets to N on every reboot. To make it stick,
use a systemd tmpfiles rule, which works whether fuse is built in or loaded as a
module:
echo 'w /sys/module/fuse/parameters/enable_uring - - - - 1' | \
sudo tee /etc/tmpfiles.d/fuse-iouring.conf
That file is applied automatically early in every boot. (If you prefer the classic
approach and fuse is a loadable module on your system — check with
lsmod | grep '^fuse' — you can instead create /etc/modprobe.d/fuse.conf containing
options fuse enable_uring=1. If fuse is built into the kernel, add
fuse.enable_uring=1 to GRUB_CMDLINE_LINUX in /etc/default/grub and run
sudo update-grub.)
Step 2 — Build a working libfuse
We'll build the current upstream libfuse and install it into its own directory,
/opt/libfuse-git, so the system's libfuse3 package is left completely untouched.
Nothing else on the machine will pick up this build unless we explicitly point it there,
which is exactly what we want.
Install the build tools and clone the sources:
sudo apt update
sudo apt install -y git meson ninja-build pkg-config gcc \
liburing-dev python3 libc6-dev
git clone --depth 1 https://github.com/libfuse/libfuse.git
cd libfuse
Configure, compile, and install into the isolated prefix. -Denable-io-uring=true is the
default upstream, but we pass it explicitly so there's no ambiguity:
meson setup build --prefix=/opt/libfuse-git --libdir=lib -Denable-io-uring=true
meson compile -C build
sudo meson install -C build
Check what you built:
PKG_CONFIG_PATH=/opt/libfuse-git/lib/pkgconfig pkg-config --modversion fuse3
# expect: 3.19.x (or newer)
You now have a working FUSE library at /opt/libfuse-git/lib/libfuse3.so.4, alongside —
but separate from — the distribution's /lib/x86_64-linux-gnu/libfuse3.so.4.
Step 3 — Install the MooseFS CE client
Try the official package first. The MooseFS 4 CE repository has a native Ubuntu 26.04
("Resolute Raccoon") build — the resolute suite — with moosefs-client for both amd64
and arm64:
curl https://repository.moosefs.com/moosefs.key | \
sudo gpg -o /etc/apt/keyrings/moosefs.gpg --dearmor
echo "deb [arch=amd64 signed-by=/etc/apt/keyrings/moosefs.gpg] http://repository.moosefs.com/moosefs-4/apt/ubuntu/resolute resolute main" | \
sudo tee /etc/apt/sources.list.d/moosefs.list
sudo apt update
sudo apt install -y moosefs-client
(On arm64, change arch=amd64 to arch=arm64.)
Now confirm the client is linked against libfuse3 (the io_uring‑capable FUSE generation) rather than the older libfuse2:
ldd "$(command -v mfsmount)" | grep -i fuse
- If you see
libfuse3.so.4, the package will work — continue to Step 4, Option A. - If you see
libfuse.so.2(libfuse 2), this build of the package can't do io_uring at all. Skip to Step 4, Option B and build the client from source against libfuse3.
Step 4 — Point the client at your libfuse build
mfsmount is dynamically linked to libfuse3.so.4, and by default the loader resolves
that to the distribution copy. We need this one binary — and nothing else on the system —
to load our /opt/libfuse-git build instead.
Option A — Retarget the packaged mfsmount (recommended)
Bake an rpath into the installed mfsmount binary so it looks in /opt/libfuse-git/lib
first. This affects only mfsmount; every other program keeps using the system libfuse.
sudo apt install -y patchelf
sudo patchelf --add-rpath /opt/libfuse-git/lib "$(command -v mfsmount)"
Verify that mfsmount now resolves libfuse to the isolated build:
ldd "$(command -v mfsmount)" | grep -i fuse
# expect a path under /opt/libfuse-git/lib/libfuse3.so.4
A future apt upgrade of moosefs-client will replace the binary and drop the rpath.
Just re‑run the patchelf command after any client upgrade (or
sudo apt-mark hold moosefs-client if you want to freeze the version).
Option B — Build the MooseFS client from source
Use this if the packaged client is libfuse2‑based, if no suitable package exists for your
release, or if you simply prefer building it yourself. Building against the isolated
libfuse guarantees mfsmount uses it, and we bake in the rpath at link time.
sudo apt install -y build-essential autoconf automake libtool pkg-config zlib1g-dev
git clone https://github.com/moosefs/moosefs.git
cd moosefs
./autogen.sh
# Point the build at the isolated libfuse and record its location in the binary:
PKG_CONFIG_PATH=/opt/libfuse-git/lib/pkgconfig \
LDFLAGS="-Wl,-rpath,/opt/libfuse-git/lib" \
./configure --disable-mfsmaster --disable-mfschunkserver \
--enable-mfsmount --prefix=/usr/local
make -j"$(nproc)"
sudo make install
Watch the ./configure summary: it should report that it found FUSE version 3.x. The
client binary lands at /usr/local/sbin/mfsmount, which keeps it clearly separate from
anything the package manager installs. Confirm the linkage the same way:
ldd /usr/local/sbin/mfsmount | grep -i fuse
# expect a path under /opt/libfuse-git/lib/libfuse3.so.4
Step 5 — Mount MooseFS with io_uring
Create a mount point and mount, adding -o io_uring:
sudo mkdir -p /mnt/mfs
sudo mfsmount /mnt/mfs -H mfsmaster -o io_uring
If your mfsmount came from Option B, call it by its full path
(/usr/local/sbin/mfsmount) or make sure /usr/local/sbin is ahead of /usr/sbin in
your PATH.
You can tune the ring depth if you like; the FUSE default is 8:
sudo mfsmount /mnt/mfs -H mfsmaster -o io_uring -o io_uring_q_depth=16
If io_uring can't be started for any reason, mfsmount reports it and falls back to the
classic FUSE transport, so the mount still succeeds — see Troubleshooting
below to find out why.
Step 6 — Confirm io_uring is actually in use
Three quick checks, from most to least direct.
The kernel switch is on:
cat /sys/module/fuse/parameters/enable_uring # Y
The running client loaded your libfuse, not the system one:
pid=$(pgrep -x mfsmount | head -n1)
grep -o '/[^ ]*libfuse3[^ ]*' "/proc/$pid/maps" | sort -u
# expect: /opt/libfuse-git/lib/libfuse3.so.4
The io_uring worker threads exist. When FUSE‑over‑io_uring is active, libfuse spins up
one ring worker thread per CPU, named fuse-ring-N:
ps -L -p "$(pgrep -x mfsmount | head -n1)" -o comm= | grep fuse-ring | sort -u
# expect: fuse-ring-0, fuse-ring-1, ...
If you see fuse-ring-* threads and the maps point at /opt/libfuse-git, io_uring is
working.
Step 7 — Make it permanent
Two things need to survive a reboot:
- The
enable_uringswitch — already handled by thetmpfiles.drule in Step 1. - The mount itself — the cleanest way is a small systemd service or an
/etc/fstabentry using the MooseFS mount helper. The exactfstabsyntax and available options are documented inman mfsmountandman mfsmount.cfg; the important part for this guide is simply to includeio_uringamong the mount options, e.g. inside/etc/mfs/mfsmount.cfgalongside yourmfsmaster=line. Whichever method you choose, if you used Option A, remember to re‑apply thepatchelfrpath after anymoosefs-clientpackage upgrade.
Troubleshooting
mfsmount prints failed to start io-uring: Unknown error -95 (or silently falls back
to classic FUSE). -95 is EOPNOTSUPP. Work through it in this order:
- Is the kernel switch on?
cat /sys/module/fuse/parameters/enable_uringmust printYbefore you mount. If it'sN, the kernel won't advertise io_uring during the FUSE handshake and the client falls back. - Which libfuse did the client actually load? Re‑run the
/proc/<pid>/mapscheck from Step 6. If it shows/lib/x86_64-linux-gnu/libfuse3.so.4(the 3.18.2 system copy), the rpath from Step 4 didn't take — the packaged binary was probably upgraded and reverted. Re‑run thepatchelfcommand. - Is io_uring restricted system‑wide? Some hardened setups disable it via sysctl:
sysctl kernel.io_uring_disabledshould be0. A value of1or2blocks io_uring regardless of everything else. - Is the client libfuse2‑based?
ldd "$(command -v mfsmount)" | grep -i fuse. If it linkslibfuse.so.2, it cannot do io_uring; use Step 4, Option B.
Everything looks right but it still falls back. Double‑check that the libfuse build reported version 3.19.x or newer in Step 2. The 3.18.2 release that ships with Ubuntu 26.04 carries only the io_uring security fixes (CVE‑2026‑33150 and CVE‑2026‑33179); the changes that make the feature usable came afterward, so an older build will not work here.
Why this dance is necessary (background)
FUSE‑over‑io_uring is a young feature. Kernel support was merged in Linux 6.14 and is
present and enabled in Ubuntu 26.04's Linux 7.0 kernel. The userspace half landed in
libfuse 3.18.0, but the first releases were rough: 3.18.1 and 3.18.2 were rushed out to fix
memory‑safety bugs in the new code path, and the work that makes the transport actually
negotiate and run correctly continued afterward toward the 3.19 release. Because Ubuntu
26.04 froze on libfuse 3.18.2, the distribution's own package can't drive io_uring
end‑to‑end even though the kernel underneath it is fully capable. Building the current
libfuse and pointing mfsmount at it bridges that gap until a fixed libfuse is packaged.
References
- MooseFS — Download & install (official repositories): https://moosefs.com/download
- MooseFS source: https://github.com/moosefs/moosefs
- libfuse (source, build options, io_uring notes): https://github.com/libfuse/libfuse
- libfuse
README.fuse-io-uring: https://github.com/libfuse/libfuse/blob/master/doc/README.fuse-io-uring - Linux kernel — FUSE‑over‑io_uring design: https://docs.kernel.org/filesystems/fuse/fuse-io-uring.html
- libfuse io_uring security fixes (context for why 3.18.2 is insufficient): CVE‑2026‑33150, CVE‑2026‑33179