๐ FreeBSD vs Linux: What Is Actually Different
You already know Linux. Here is the short, tested tour of what changes when the machine in front of you runs FreeBSD
Same family, different branch
Both Linux and FreeBSD behave like Unix: files and folders, users and permissions, processes, pipes, grep, ssh and shell scripts all work in the way you already know. The difference is in the family tree. BSD grew out of the Unix work done at the University of California, Berkeley, and FreeBSD is one of its living descendants. Linux was written from scratch, years later, to behave like Unix.
You will find FreeBSD in places where people care about networking, storage and long, quiet uptimes: firewalls and routers (pfSense and OPNsense are built on it), storage appliances, content delivery networks and plenty of ordinary servers. It is often the system at the other end of a "can you upgrade that box?" request, which is how this corner of the site came about.
Everything below was run on a real FreeBSD 15.0-RELEASE installed in a virtual machine with the default ZFS layout. Numbers and names on your machine will differ.
One system, not a kit
Linux is a kernel. A distribution (Ubuntu, Rocky, Debian) collects a kernel from one project, a C library from another, the ls and grep tools from a third, and so on. FreeBSD is developed as one whole operating system: the kernel and the basic tools ("the base system") live in the same source tree and are released together under one version number.
bsd@practice:~ $ uname -a FreeBSD practice 15.0-RELEASE FreeBSD 15.0-RELEASE releng/15.0-n280995-7aedc8de6446 GENERIC amd64 bsd@practice:~ $ freebsd-version -kru 15.0-RELEASE 15.0-RELEASE 15.0-RELEASE bsd@practice:~ $ head -3 /etc/os-release NAME=FreeBSD VERSION="15.0-RELEASE" VERSION_ID="15.0" bsd@practice:~ $ echo $SHELL /bin/sh bsd@practice:~ $ ls / COPYRIGHT etc mnt sbin zroot bin home net sys boot lib proc tmp dev libexec rescue usr entropy media root var
- uname -a names the system, the host, the release and the build. There is no "distribution" line to look for: the OS is FreeBSD, and 15.0-RELEASE is its version.
- freebsd-version -kru prints three versions: the kernel that is installed, the running kernel and the userland (the tools). They are normally identical, and during an upgrade they briefly are not. That is handy, and the upgrade page in this corner uses it.
- /etc/os-release exists here too, so the scripts that look for it keep working.
- The login shell is /bin/sh, a small plain shell, not bash. Your muscle memory for arrow-key history and Tab completion works in many cases, but bash features such as arrays or [[ ]] do not.
- Look at ls /: no lib64, no snap, but a rescue folder holding a tiny statically linked toolkit for emergencies, and zroot, the top of the ZFS storage pool you will meet shortly.
Base system and packages are kept apart
On Linux, /usr/bin holds almost everything, whoever supplied it. On FreeBSD the base system lives in /bin, /sbin, /usr/bin and /usr/sbin, and everything you add yourself goes under /usr/local, so you can always tell what shipped with the OS and what you installed.
โ ๏ธ 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).
bsd@practice:~ $ which ls ps sh vi /bin/ls /bin/ps /bin/sh /usr/bin/vi bsd@practice:~ $ ls /usr/local bsd@practice:~ $ pkg -v The package management tool is not yet installed on your system. Do you want to fetch and install it now? [y/N]: n bsd@practice:~ $ which sudo bash nano curl
- which shows the base tools living in the usual places. The last which sudo bash nano curl printed nothing, because none of them is part of the base system. FreeBSD does ship vi, ee (a friendly beginner editor, the equivalent of nano) and fetch (the base system's way to download a file).
- /usr/local is empty on a new install.
- Packages come from pkg. On a fresh install, typing pkg offers to download the package tool itself the first time. We answered n. Once it is installed, pkg install nano, pkg search, pkg upgrade and pkg remove work like the apt commands you know (from the documentation, not run here).
- There is also the ports tree, a collection of recipes for building software from source. Most people use pkg (mittens), and ports (gloves) are for when you need your own options: the ports tree page builds software both ways.
Services: /etc/rc.conf, not systemd
FreeBSD has no systemd. Services are started by a set of plain shell scripts in /etc/rc.d (the base system) and /usr/local/etc/rc.d (packages), and what runs at boot is set in one text file, /etc/rc.conf.
bsd@practice:~ $ cat /etc/rc.conf zfs_enable="YES" hostname="practice" ifconfig_vtnet0="DHCP" sshd_enable="YES" bsd@practice:~ $ sysrc hostname hostname: practice bsd@practice:~ $ sysrc sshd_enable sshd_enable: YES bsd@practice:~ $ service -e /etc/rc.d/hostid /etc/rc.d/hostid_save /etc/rc.d/zpool /etc/rc.d/kldxref /etc/rc.d/zfsbe /etc/rc.d/var_run [... 25 more lines left out ...] bsd@practice:~ $ service sshd status sshd is running as pid 1243. bsd@practice:~ $ ls /etc/rc.d | head -8 DAEMON FILESYSTEMS LOGIN NETWORKING SERVERS accounting adjkerntz apm
- cat /etc/rc.conf is the whole story of this machine: ZFS on, a host name, DHCP on the network card, the SSH server on. Four lines, and you can read every one.
- sysrc is the safe way to read or change that file: sysrc hostname reads a value, sysrc sshd_enable=YES sets one (from the documentation, not run here), and it never damages the file's layout.
- service sshd status is the systemctl status equivalent. The same word, service, handles start, stop, restart and reload, and service -e lists every service that is enabled.
- Naming rule: a service called nginx is switched on at boot with sysrc nginx_enable=YES. Installing a package never turns it on by itself.
Users, groups and the missing sudo
bsd@practice:~ $ id uid=1001(bsd) gid=1001(bsd) groups=0(wheel),1001(bsd) bsd@practice:~ $ su - Password: root@practice:~ # whoami root root@practice:~ # pw groupshow wheel wheel:*:0:root,bsd root@practice:~ # sudo ls -su: sudo: not found root@practice:~ # exit
- id shows the user is in the group wheel, with the number 0. Members of wheel are the only people allowed to become root with su. (The Rocky page in Linux 101 shows the same group name.)
- su - asks for root's password, not your own, and gives you a root login. That is the traditional BSD way. Note the prompt changes from $ to #.
- pw is the user-management tool of FreeBSD: pw groupshow wheel shows the group and its members, and pw useradd and pw usermod create and change users (the installer used them to make this practice user).
- sudo is not installed: -su: sudo: not found. It is available as a package, and so is doas, a smaller alternative. Many FreeBSD administrators simply use su -.
ZFS and boot environments: the undo button
FreeBSD's installer offers ZFS, a filesystem and volume manager in one. It is a first-class citizen here, and the default layout is built to make upgrades safe. (The ZFS in practice page goes much deeper.)
bsd@practice:~ $ zpool list NAME SIZE ALLOC FREE CKPOINT EXPANDSZ FRAG CAP DEDUP HEALTH ALTROOT zroot 13.5G 460M 13.1G - - 0% 3% 1.00x ONLINE - bsd@practice:~ $ zfs list NAME USED AVAIL REFER MOUNTPOINT zroot 460M 12.6G 96K /zroot zroot/ROOT 458M 12.6G 96K none zroot/ROOT/default 458M 12.6G 458M / zroot/home 136K 12.6G 136K /home zroot/tmp 104K 12.6G 104K /tmp zroot/usr 288K 12.6G 96K /usr zroot/usr/ports 96K 12.6G 96K /usr/ports zroot/usr/src 96K 12.6G 96K /usr/src zroot/var 608K 12.6G 96K /var zroot/var/audit 96K 12.6G 96K /var/audit zroot/var/crash 96K 12.6G 96K /var/crash zroot/var/log 128K 12.6G 128K /var/log zroot/var/mail 96K 12.6G 96K /var/mail zroot/var/tmp 96K 12.6G 96K /var/tmp bsd@practice:~ $ bectl list BE Active Mountpoint Space Created default NR / 458M 2026-10-05 20:58
- zpool list shows the pool, zroot, the shared storage: 13.5 GB with 3 percent used.
- zfs list shows the datasets inside it. Each is like a folder with its own settings, snapshots and quotas, and all draw from the same free space. /home, /var/log, /tmp and /usr/ports are separate datasets, so you can snapshot or limit one without touching the others.
- zroot/ROOT/default is the root of the running system. And bectl list shows it as a boot environment (BE) named default. The Active column says NR: Now (it is running) and Reboot (it is the one used at next boot).
A boot environment is a snapshot of the whole operating system that you can boot into. Here is the idea in use:
bsd@practice:~ $ su - Password: root@practice:~ # bectl create before-experiment root@practice:~ # bectl list BE Active Mountpoint Space Created before-experiment - - 8K 2026-10-05 21:02 default NR / 458M 2026-10-05 20:58 root@practice:~ # bectl destroy -F before-experiment root@practice:~ # bectl list BE Active Mountpoint Space Created default NR / 458M 2026-10-05 20:58 root@practice:~ # exit
bectl create before-experiment made a copy, and the space used is 8K, because ZFS only stores differences. It shows up in the list. We then removed it. Before an upgrade you create a BE, and if the upgrade goes badly, you boot the old one. That is the undo button Linux administrators dream about, and the reason the upgrade page in this corner starts here.
Network and system tools
bsd@practice:~ $ ifconfig vtnet0 vtnet0: flags=1008843<UP,BROADCAST,RUNNING,SIMPLEX,MULTICAST,LOWER_UP> metric 0 mtu 1500 options=880028<VLAN_MTU,JUMBO_MTU,LINKSTATE,HWSTATS> ether 52:54:00:12:34:56 inet 10.0.2.15 netmask 0xffffff00 broadcast 10.0.2.255 media: Ethernet autoselect (10Gbase-T <full-duplex>) status: active nd6 options=29<PERFORMNUD,IFDISABLED,AUTO_LINKLOCAL> bsd@practice:~ $ netstat -rn | head -6 Routing tables Internet: Destination Gateway Flags Netif Expire default 10.0.2.2 UGS vtnet0 10.0.2.0/24 link#1 U vtnet0 bsd@practice:~ $ sockstat -4l USER COMMAND PID FD PROTO LOCAL ADDRESS FOREIGN ADDRESS root sshd 1243 7 tcp4 *:22 *:* root syslogd 1057 7 udp4 *:514 *:* bsd@practice:~ $ ping -c 2 10.0.2.2 PING 10.0.2.2 (10.0.2.2): 56 data bytes 64 bytes from 10.0.2.2: icmp_seq=0 ttl=255 time=0.411 ms 64 bytes from 10.0.2.2: icmp_seq=1 ttl=255 time=0.242 ms --- 10.0.2.2 ping statistics --- 2 packets transmitted, 2 packets received, 0.0% packet loss round-trip min/avg/max/stddev = 0.242/0.327/0.411/0.085 ms
- ifconfig is still the network tool, and it is the one in use every day (Linux replaced it with ip). Network cards are named after their driver, not eth0: vtnet0 is the virtual card here, em0 is an Intel card, igb0 another Intel family.
- netstat -rn shows the routing table: the default gateway is 10.0.2.2.
- sockstat -4l is the answer to "what is listening?", the job of ss on Linux. It names the user and the program for each open port with no sudo needed. Here it shows sshd on port 22 and syslogd.
- ping -c 2 works as you know it.
bsd@practice:~ $ sysctl hw.ncpu hw.physmem kern.ostype kern.osrelease hw.ncpu: 2 hw.physmem: 2102890496 kern.ostype: FreeBSD kern.osrelease: 15.0-RELEASE bsd@practice:~ $ uptime 9:02PM up 3 mins, 1 user, load averages: 0.11, 0.04, 0.01 bsd@practice:~ $ kldstat | head -5 Id Refs Address Size Name 1 15 0xffffffff80200000 1f4daa0 kernel 2 1 0xffffffff8214f000 620c10 zfs.ko 3 1 0xffffffff83018000 4250 ichsmb.ko 4 1 0xffffffff8301d000 2178 smbus.ko bsd@practice:~ $ ls /proc
- sysctl reads and sets kernel settings: processors, memory, version and thousands of others. sysctl -a lists them all.
- uptime is the same, with "load averages" as in lesson 9 of Linux 101.
- kldstat lists the loaded kernel modules, the lsmod of FreeBSD. zfs.ko is the ZFS driver.
- ls /proc is empty: the /proc folder that Linux fills with information is not mounted by default here. sysctl and ps give you the same facts.
Logs are plain text again: /var/log/messages is the main system log, written by syslogd and rotated by newsyslog. There is no journalctl: use tail, less and grep (lesson 4 of Linux 101).
Linux commands that are not there
If you type your usual Linux commands, most of the time you get a polite refusal:
bsd@practice:~ $ ip addr -sh: ip: not found bsd@practice:~ $ free -h -sh: free: not found bsd@practice:~ $ ss -tln -sh: ss: not found bsd@practice:~ $ systemctl status sshd -sh: systemctl: not found bsd@practice:~ $ apt install nano -sh: apt: not found bsd@practice:~ $ journalctl -u sshd -sh: journalctl: not found bsd@practice:~ $ which service sysrc ifconfig sockstat pkg /usr/sbin/service /usr/sbin/sysrc /sbin/ifconfig /usr/bin/sockstat /usr/sbin/pkg
The last line shows the FreeBSD tools that answer the same questions. Here is the map:
- ip addr โ ifconfig. ip route โ netstat -rn.
- ss -tln โ sockstat -4l (or sockstat -l).
- systemctl status NAME โ service NAME status. Enable at boot: systemctl enable NAME โ sysrc NAME_enable=YES.
- journalctl โ tail /var/log/messages.
- apt install โ pkg install. apt update && apt upgrade โ pkg update && pkg upgrade.
- free -h โ sysctl hw.physmem and top (there is a memory summary at the top).
- sudo โ su - (or install sudo or doas).
- lsmod โ kldstat. dmesg is the same.
- ufw or firewalld โ pf, FreeBSD's firewall, set up in /etc/pf.conf (the pf page tests it for real).
- docker containers โ jails: FreeBSD's built-in isolated environments, in the system for over twenty years (the jails page builds one for real). bhyve is the built-in hypervisor for virtual machines (the bhyve page boots a real guest).
- Linux programs themselves: FreeBSD can run many of them directly through its Linux compatibility layer, with no virtual machine (the Linux compatibility page does it for real).
Little gotchas for Linux hands
The tools in FreeBSD's base system come from BSD, not the GNU project, so a few familiar options differ:
bsd@practice:~ $ echo hello > t.txt
bsd@practice:~ $ sed -i 's/hello/bye/' t.txt
sed: 2: "t.txt
": undefined label '.txt'
bsd@practice:~ $ sed -i '' 's/hello/bye/' t.txt
bsd@practice:~ $ cat t.txt
bye
bsd@practice:~ $ date -d yesterday
date: illegal option -- d
usage: date [-jnRu] [-I[date|hours|minutes|seconds|ns]] [-f input_fmt]
[ -z output_zone ] [-r filename|seconds] [-v[+|-]val[y|m|w|d|H|M|S]]
[[[[[[cc]yy]mm]dd]HH]MM[.SS] | new_date] [+output_fmt]
- sed -i needs an argument after it, the name of a backup file. The GNU habit sed -i 's/a/b/' file made FreeBSD's sed take the script as the backup name and the file name as a script, so we got a baffling error about an "undefined label". The fix is to give an empty backup name: sed -i '' 's/hello/bye/' t.txt. cat then shows the change.
- date -d yesterday does not exist. FreeBSD's date calls it -v: date -v-1d means "one day ago" (from the documentation, not run here).
- Some tools are the same but the options are not identical: ps aux and tar work, ls --color has the short form ls -G. When something fails, read its usage line, and look at its man page (man sed). The FreeBSD man pages are excellent, and man hier explains the whole directory layout.
None. It is already in the base system.
๐ก Safe to experiment: a virtual machine is the right place to learn this. You can install FreeBSD in VirtualBox, VMware, QEMU or Proxmox in about ten minutes. Choose the ZFS option in the installer and you get boot environments for free.
๐งช Try it yourself
On a FreeBSD machine or VM:
1. Show the kernel and userland versions. Are they equal? 2. Which file decides which services start at boot? Print it. 3. Is sshd running? Use service. 4. Which user and program is listening on each TCP port? (sockstat) 5. List the ZFS datasets and the boot environments. 6. Create a boot environment called practice-1, list it, then destroy it. 7. Print the routing table and find your default gateway.
The cheat sheet
- Versions: uname -a, freebsd-version -kru.
- Services: service NAME status|start|stop|restart, service -e, sysrc NAME_enable=YES, file /etc/rc.conf.
- Packages: pkg install NAME, pkg search NAME, pkg upgrade. Your software lives in /usr/local.
- Users: group wheel, su -, pw useradd and pw groupshow.
- ZFS: zpool list, zfs list, bectl list, bectl create NAME, bectl destroy NAME.
- Network: ifconfig, netstat -rn, sockstat -4l, ping.
- System: sysctl, kldstat, top, tail /var/log/messages.
- Gotchas: sed -i '' (empty backup name), date -v-1d, no bash, no sudo by default.
↑ FreeBSD Corner · Upgrading FreeBSD Safely: freebsd-update, pkg and Boot Environments →