🐧 Linux Binary Compatibility: Running Linux Programs on FreeBSD

FreeBSD can run Linux programs directly, with no virtual machine. Switch it on, install a Rocky Linux userland, run real Linux software, and find where the compatibility ends

Video: Linux Binary Compatibility (FreeBSD video)
Prefer to watch? This short video runs this page's main commands on a real FreeBSD 15.0 machine, with on-screen captions and no voiceover. Click to play, or watch it on YouTube.

What it is

The FreeBSD kernel contains a Linux ABI layer, nicknamed the linuxulator. A Linux program makes system calls to the kernel to open files, allocate memory and send data, and it expects a Linux kernel to answer. The layer sits in the FreeBSD kernel, translates each Linux system call into the matching FreeBSD one, and answers in the way the Linux program expects.

This is not emulation of a processor and not a virtual machine. The Linux program runs natively on the CPU, at full speed, as an ordinary FreeBSD process: you see it in ps, you can limit it, trace it and kill it like any other. (Windows' WSL 1 used a similar idea.) Compare the other ways to run Linux on FreeBSD:

Two ingredients are needed: the kernel modules that do the translating, and a Linux userland: the libraries and programs of a Linux distribution, kept in a folder, /compat/linux. Everything below was run for real on FreeBSD 15.0-RELEASE in a virtual machine.

Step 1: switch the layer on

root@practice:~ # uname -sr
FreeBSD 15.0-RELEASE
root@practice:~ # kldstat | grep -i linux
root@practice:~ # ls /compat
ls: /compat: No such file or directory
root@practice:~ # sysctl compat.linux.osname
sysctl: unknown oid 'compat.linux.osname'

A fresh FreeBSD has no Linux anything: no modules loaded, no /compat folder, and the Linux settings do not exist (unknown oid).

root@practice:~ # sysrc linux_enable=YES
linux_enable: NO -> YES
root@practice:~ # service linux start
root@practice:~ # kldstat | grep -i linux
10    1 0xffffffff8305a000    32320 linux.ko
12    4 0xffffffff83094000     ce68 linux_common.ko
13    1 0xffffffff830a1000    2e3f0 linux64.ko
root@practice:~ # mount | grep -E "linprocfs|linsysfs|compat"
linprocfs on /compat/linux/proc (linprocfs, local)
linsysfs on /compat/linux/sys (linsysfs, local)
devfs on /compat/linux/dev (devfs)
fdescfs on /compat/linux/dev/fd (fdescfs)
tmpfs on /compat/linux/dev/shm (tmpfs, local)
root@practice:~ # sysctl compat.linux.osname compat.linux.osrelease
compat.linux.osname: Linux
compat.linux.osrelease: 5.15.0

Step 2: install a Linux userland

⚠️ Newer package repository (checked October 2026): the package repository now targets FreeBSD 15.1, while 15.0-RELEASE is end of life. On a 15.0 machine pkg may stop with Newer FreeBSD version for package ... [y/N], and the version numbers in the output below may differ from yours. To carry on, run export IGNORE_OSVERSION=yes first, or better, move to a supported release (see the upgrading guide).

root@practice:~ # pkg install -y linux_base-rl9 2>&1 | grep -E "linux|Number|download|space"
	linux_base-rl9: 9.8 [FreeBSD-ports]
Number of packages to be installed: 1
The process will require 361 MiB more space.
57 MiB to be downloaded.
[1/1] Fetching linux_base-rl9-9.8: .......... done
[1/1] Installing linux_base-rl9-9.8...
[1/1] Extracting linux_base-rl9-9.8: .......... done
root@practice:~ # ls /compat/linux
afs	bin	dev	etc	lib	lib64	opt	proc	run	sbin	srv	sys	usr	var
root@practice:~ # cat /compat/linux/etc/os-release | head -4
NAME="Rocky Linux"
VERSION="9.7 (Blue Onyx)"
ID="rocky"
ID_LIKE="rhel centos fedora"
root@practice:~ # du -sh /compat/linux
206M	/compat/linux
root@practice:~ # ls -l /compat/linux/bin /compat/linux/lib64 | head -6
lrwxr-xr-x  1 root wheel 7 Nov  3  2024 /compat/linux/bin -> usr/bin
lrwxr-xr-x  1 root wheel 9 Nov  3  2024 /compat/linux/lib64 -> usr/lib64

linux_base-rl9 is the base set of a Rocky Linux 9 userland (package version 9.8; its os-release still says 9.7) (the same family as the Rocky page in Linux 101: a free rebuild of RHEL). One package, 57 MiB to download, which unpacks into /compat/linux. A CentOS 7 base set is also offered (linux_base-c7: from the package list, not installed here).

/compat/linux now looks like the root of a Linux machine: bin, etc, lib64, usr, var and so on. Its os-release says Rocky Linux 9.7 (Blue Onyx). bin and lib64 are links into usr, as on modern Linux. The folder takes about 206 MB. Why Rocky 9? Software for Red Hat's family is built for the same libraries, so it is the most widely compatible base (from the documentation, not run here).

Step 3: run Linux programs

root@practice:~ # uname -a
FreeBSD practice 15.0-RELEASE FreeBSD 15.0-RELEASE releng/15.0-n280995-7aedc8de6446 GENERIC amd64
root@practice:~ # /compat/linux/bin/uname -a
Linux practice 5.15.0 FreeBSD 15.0-RELEASE releng/15.0-n280995-7aedc8de6446 GENERIC x86_64 x86_64 x86_64 GNU/Linux
root@practice:~ # file /compat/linux/bin/uname | cut -c1-118
/compat/linux/bin/uname: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib
root@practice:~ # brandelf /compat/linux/bin/uname
File '/compat/linux/bin/uname' is of brand 'SVR4' (0).
root@practice:~ # brandelf /bin/ls
File '/bin/ls' is of brand 'FreeBSD' (9).
root@practice:~ # chroot /compat/linux /bin/bash -c 'cat /etc/os-release | head -2; uname -sr; echo bash $BASH_VERSION'
NAME="Rocky Linux"
VERSION="9.7 (Blue Onyx)"
Linux 5.15.0
bash 5.1.8(1)-release
root@practice:~ # chroot /compat/linux /usr/bin/ldd /bin/bash
	linux-vdso.so.1 (0x00007fffffffd000)
	libtinfo.so.6 => /lib64/libtinfo.so.6 (0x00000008011b7000)
	libc.so.6 => /lib64/libc.so.6 (0x0000000801200000)
	/lib64/ld-linux-x86-64.so.2 (0x0000000001021000)
root@practice:~ # cat /compat/linux/proc/version
Linux version 5.15.0 (des@freebsd.org) (gcc version FreeBSD Clang 19.1.7 (https://github.com/llvm/llvm-project.git llvmorg-19.1.7-0-gcd708029e0b2)) #4 Sun Dec 18 04:30:00 CET 1977
root@practice:~ # chroot /compat/linux /bin/cat /proc/meminfo | head -3
MemTotal:   3102180 kB
MemFree:    1998464 kB
Buffers:          0 kB

Read this one from the top:

The translation, caught in the act

root@practice:~ # truss /compat/linux/bin/uname 2>&1 | head -9
linux_brk(0x0)					 = 16949248 (0x102a000)
linux_arch_prctl(0x3001,0x7fffffffbba0)		 = 0 (0x0)
linux_mmap2(0x0,0x2000,0x3,0x22,0xffffffff,0x0)	 = 34376921088 (0x801063000)
linux_newuname(0x7fffffffb790)			 = 0 (0x0)
linux_access("/etc/ld.so.preload",R_OK)		 ERR#-2 'No such file or directory'
linux_openat(0xffffff9c,0x801055266,0x80000,0x0) = 3 (0x3)
linux_newfstat(3,0x7fffffffadd0)		 = 0 (0x0)
linux_mmap2(0x0,0x256b,0x1,0x2,0x3,0x0)		 = 34376929280 (0x801065000)
close(3)					 = 0 (0x0)

truss is FreeBSD's system-call tracer. Run on a Linux program, it shows the Linux system calls by name: linux_brk, linux_mmap2, linux_newuname, linux_openat. These are the translator's entry points. The program asked for uname, and linux_newuname answered. A call that FreeBSD already does the same way (close) is handled directly, and ERR#-2 'No such file or directory' is just the program looking for an optional file that is not there. This is what "running Linux programs natively" means: every system call goes through a small adapter, and everything else runs as it is.

A real Linux program, and two real snags

Now something useful: the Linux curl, built for Rocky. FreeBSD's base system has no curl at all (which curl found nothing), so this is a Linux program doing something the host cannot do by itself. We installed the package linux-rl9-curl (25 MiB with its libraries, from the package list), copied the host's /etc/resolv.conf into the Linux folder (it had none, and Linux programs need it to look up names), and then:

root@practice:~ # chroot /compat/linux /usr/bin/curl --version | head -2
/usr/bin/curl: error while loading shared libraries: libevent-2.1.so.7: cannot open shared object file: No such file or directory
root@practice:~ # chroot /compat/linux /usr/bin/ldd /usr/bin/curl | grep 'not found'
	libevent-2.1.so.7 => not found
	libevent-2.1.so.7 => not found
root@practice:~ # pkg search -x 'linux-rl9.*libevent'
linux-rl9-libevent-2.1.12_2    API for executing callback functions on events or timeouts (Rocky Linux 9.8)
root@practice:~ # pkg install -y linux-rl9-libevent 2>&1 | grep -E "linux|Number"
	linux-rl9-libevent: 2.1.12_2 [FreeBSD-ports]
Number of packages to be installed: 1
[1/1] Fetching linux-rl9-libevent-2.1.12_2: .......... done
[1/1] Installing linux-rl9-libevent-2.1.12_2...
[1/1] Extracting linux-rl9-libevent-2.1.12_2: .......... done
root@practice:~ # chroot /compat/linux /usr/bin/curl --version | head -2
curl 7.76.1 (x86_64-redhat-linux-gnu) libcurl/7.76.1 OpenSSL/3.5.5 zlib/1.2.11 brotli/1.0.9 libidn2/2.3.0 libpsl/0.21.1 (+libidn2/2.3.0) libssh/0.10.4/openssl/zlib nghttp2/1.43.0
Release-Date: 2021-04-14
root@practice:~ # chroot /compat/linux /usr/bin/curl -sSI https://techcommune.org 2>&1 | head -3
curl: (77) error setting certificate file: /etc/pki/tls/certs/ca-bundle.crt
root@practice:~ # chroot /compat/linux /usr/bin/getent hosts techcommune.org
47.204.6.48     techcommune.org
root@practice:~ # pkg install -y linux-rl9-ca-certificates 2>&1 | grep -E "linux|Number"
	linux-rl9-ca-certificates: 2025.2.80 [FreeBSD-ports]
	linux-rl9-libtasn1: 4.16.0_2 [FreeBSD-ports]
	linux-rl9-p11-kit: 0.26.2 [FreeBSD-ports]
Number of packages to be installed: 3
[1/3] Fetching linux-rl9-libtasn1-4.16.0_2: .......... done
[2/3] Fetching linux-rl9-p11-kit-0.26.2: .......... done
[3/3] Fetching linux-rl9-ca-certificates-2025.2.80: .......... done
[1/3] Installing linux-rl9-libtasn1-4.16.0_2...
[1/3] Extracting linux-rl9-libtasn1-4.16.0_2: .......... done
[2/3] Installing linux-rl9-p11-kit-0.26.2...
[2/3] Extracting linux-rl9-p11-kit-0.26.2: .......... done
[3/3] Installing linux-rl9-ca-certificates-2025.2.80...
[3/3] Extracting linux-rl9-ca-certificates-2025.2.80: .......... done
root@practice:~ # chroot /compat/linux /usr/bin/curl -sSI https://techcommune.org | head -4
HTTP/1.1 200 OK
Server: nginx
Date: Sat, 24 Oct 2026 23:53:09 GMT
Content-Type: text/html
root@practice:~ # which curl || echo "no curl on the FreeBSD side"
no curl on the FreeBSD side

Two typical snags, one after another:

  1. error while loading shared libraries: libevent-2.1.so.7. A Linux program needs a library that is not in /compat/linux. This is the most common problem. The way to find out what is missing is ldd inside the Linux folder: libevent-2.1.so.7 => not found. Then search for the FreeBSD package that holds a Linux library: pkg search -x 'linux-rl9.*libevent' finds linux-rl9-libevent. Install it, and curl --version prints curl 7.76.1 (x86_64-redhat-linux-gnu): the genuine Rocky build.
  2. curl: (77) error setting certificate file: /etc/pki/tls/certs/ca-bundle.crt. The program is fine, but a Linux program looks for the Linux list of trusted certificates, in the Linux place, and FreeBSD's own list is in the wrong folder. The Linux package linux-rl9-ca-certificates brings it. (Name lookups were never the problem: getent hosts techcommune.org from inside the Linux folder answered at once, 47.204.6.48.)

After that, the Linux curl fetched this very website over HTTPS and got HTTP/1.1 200 OK. The pattern is always the same: run it, read the error, find the missing Linux package, install it. The linux-rl9-* packages cover a long list of libraries, from graphics and fonts to compilers (from the package list).

Where the compatibility ends

root@practice:~ # ls /compat/linux/usr/bin | wc -l
     263
root@practice:~ # chroot /compat/linux /usr/bin/systemctl status 2>&1 | head -2
chroot: /usr/bin/systemctl: No such file or directory
root@practice:~ # chroot /compat/linux /bin/ls /sys/fs/cgroup 2>&1 | head -2
/bin/ls: cannot access '/sys/fs/cgroup': No such file or directory
root@practice:~ # chroot /compat/linux /bin/ls /proc/self/ns 2>&1 | head -2
/bin/ls: cannot access '/proc/self/ns': No such file or directory

And the boot-time setting, as we saw it:

root@practice:~ # grep -n "linux" /etc/rc.conf
5:linux_enable="YES"
root@practice:~ # service linux describe 2>&1 | head -2
Enable Linux ABI

Other things to know

All from the documentation, not run here:

When to choose which

⚠️ Test before you rely on it. Compatibility is very good, not perfect. A program that starts and runs is not proof that every feature of it works. Try the actions you need (and watch truss for unimplemented calls) before putting a Linux program into production on FreeBSD.

Why was the penguin comfortable on FreeBSD?<br>
Someone had told it the kernel was 5.15.0,<br>
and it did not ask any more questions.

🧪 Try it yourself

On a FreeBSD machine:

1. Enable the layer with sysrc linux_enable=YES and service linux start. Which modules loaded, and what do the compat.linux sysctls say? 2. Install linux_base-rl9 and read /compat/linux/etc/os-release. 3. Run the Linux uname -a and compare it with the FreeBSD one. Then chroot /compat/linux /bin/bash and look around. 4. Use truss on the Linux uname and find a linux_ system call. 5. Install linux-rl9-curl. When it complains about a missing library, find it with ldd and pkg search. 6. Fetch a web page over HTTPS with it. What was missing the second time? 7. Look for systemctl, /sys/fs/cgroup and /proc/self/ns in the Linux folder. What does that tell you about running Docker?

The cheat sheet

← ipfw: The Other FreeBSD Firewall · ↑ FreeBSD Corner · The Boot Loader and Kernel Tuning: loader.conf, sysctl and Single-User Mode →