Inside the FreeBSD Boot Process: BIOS/UEFI, the Loader, and init
A forensic walkthrough of FreeBSD startup from BIOS or UEFI firmware through loader, kernel initialization, init, and ordered rc services.
FreeBSD’s boot sequence is unusually inspectable: firmware, bootstrap code, loader, kernel, init(8), and the rc(8) system each own a distinct part of startup. The boundaries matter during recovery. A machine that cannot find loader has a different class of failure from one that loads the kernel but cannot mount root, and both differ from a system that reaches multi-user mode with one failed service.
The exact early path depends on architecture, firmware, partitioning, and filesystem. The classic three-stage BIOS description remains useful for understanding x86 bootstrap constraints, but it should not be applied literally to a modern UEFI system. The running machine’s partition table and firmware mode are evidence; a generic diagram is not.
Stage 1 and 2: the boot blocks
In the traditional BIOS/MBR path documented by the Handbook, boot0 is an optional boot manager in the Master Boot Record. It selects a bootable slice and transfers control to the FreeBSD bootstrap installed there. The combined /boot/boot image contains the constrained boot1 and more capable boot2 stages: boot1 understands enough of the disk label to find boot2, and boot2 understands enough of the filesystem to locate /boot/loader or a kernel.
GPT BIOS installations use GPT-specific bootstrap components rather than treating an MBR recipe as interchangeable. Never reinstall bootcode from an article without first recording the actual layout:
gpart show -p ada0
mount
kenv currdev
gpart show -p reveals the partition scheme and provider names. kenv currdev shows the loader’s notion of its current device after boot. Repair commands such as gpart bootcode are intentionally absent here because their arguments differ between BIOS/GPT, BIOS/MBR, and UEFI layouts and can make a recoverable disk unbootable when copied blindly.
UEFI: firmware, the ESP, and loader.efi
UEFI firmware reads an executable from the EFI System Partition instead of executing 512-byte BIOS stages. FreeBSD’s EFI loader is conventionally installed under an EFI path on the ESP and then loads the kernel and modules from the FreeBSD filesystem. Firmware boot variables may point to that file, while the standardized fallback path is useful when firmware entries are missing.
The ESP is a FAT filesystem, not /boot itself. Mounting it read-only during diagnosis lets an administrator confirm what is present without confusing the loader copy on the ESP with files below /boot:
gpart show -p ada0
mkdir -p /mnt/esp
mount -t msdosfs -o ro /dev/ada0p1 /mnt/esp
find /mnt/esp/EFI -maxdepth 3 -type f
umount /mnt/esp
Provider names are examples and must be taken from gpart show. A firmware entry that points at an old loader copy can explain why replacing /boot/loader.efi appeared to have no effect. Conversely, updating only the ESP cannot repair a damaged kernel or root filesystem.
Stage 3: /boot/loader
/boot/loader is the final bootstrap stage. It can read supported filesystems, choose a kernel, load modules, pass an environment to the kernel, and present an interactive menu. Current systems commonly use the Lua-based menu and scripting environment, but administrators should rely on loader’s documented built-in commands rather than assume one interpreter implementation across every supported release.
\ FreeBSD/x86 boot
\ Default: boot
\ Autoboot in 3 seconds
Interrupting autoboot drops to the loader prompt, where lsdev, ls, lsmod, show, set, unset, load, unload, and boot expose what will be handed to the kernel. Changes made there are temporary:
OK boot -s # boot to single-user mode
OK lsdev
OK lsmod
OK set hint.hpt27xx.0.disabled=1
OK show
Booting the previous kernel should be explicit because loader may already have loaded the normal kernel and modules:
OK unload
OK load /boot/kernel.old/kernel
OK boot
The kernel.old directory is normally produced during kernel installation, but its existence is not guaranteed. Verify it with loader’s ls command. On a ZFS-root installation, a known-good boot environment selected through the boot menu or inspected later with bectl list can provide a more complete rollback because it preserves matching kernel and userland state.
During initialization, loader reads /boot/loader.rc; the default flow reads /boot/defaults/loader.conf and then local overrides in /boot/loader.conf. The defaults file belongs to the base system and should not be edited. /boot/loader.conf is appropriate for modules and tunables that must exist before the kernel initializes, while runtime settings belong elsewhere when the relevant manual page says they can be changed later.
# /boot/loader.conf
zfs_load="YES"
vfs.zfs.arc_max="2G"
Loader tunables are strings at this stage, and not every runtime sysctl is a valid loader tunable. Confirm scope in the variable’s manual page. After startup, kenv displays the loader environment inherited by the kernel; sysctl displays the kernel’s current state. They answer related but different questions.
Stage 4: the kernel and devd
After loader transfers control, the kernel initializes memory management, buses, drivers, and devices, then selects and mounts the root filesystem. The -v boot flag makes device probing more verbose; -a asks interactively for the root device; -s requests single-user mode. These flags are often safer diagnostic tools than permanently changing loader configuration before the cause is known.
When kernel initialization finishes, it starts /sbin/init as PID 1, unless init_path directs otherwise. This is also the correction to a common sequencing error: devd is a userland daemon started by the rc system. Device enumeration begins in the kernel, but devd does not run before init.
Useful post-boot evidence includes:
dmesg -a
less /var/run/dmesg.boot
sysctl kern.boottime
kenv | sort
/var/run/dmesg.boot preserves boot-time kernel messages better than relying on the current ring buffer alone. Neither file replaces console capture for a panic that occurs before the root filesystem becomes writable.
Stage 5: init and rc
In multi-user startup, init invokes /etc/rc. The rc framework orders base-system scripts from /etc/rc.d and package scripts from /usr/local/etc/rc.d by metadata such as PROVIDE, REQUIRE, BEFORE, and KEYWORD. Filename order is not the dependency model; rcorder(8) computes it.
#!/bin/sh
# PROVIDE: nginx
# REQUIRE: LOGIN
# KEYWORD: shutdown
. /etc/rc.subr
name="nginx"
rcvar="nginx_enable"
load_rc_config $name
run_rc_command "$1"
Services commonly expose an _enable variable in /etc/rc.conf, but scripts may also consume flags, profiles, and service-specific settings. Use sysrc so quoting and replacement are handled consistently:
sysrc nginx_enable=YES
sysrc sshd_enable=YES
service nginx start
service nginx status
service -e
rcorder /etc/rc.d/* /usr/local/etc/rc.d/* 2>/dev/null
service -e lists enabled services; it does not prove they are healthy. A package service that starts manually but not during boot may have an ordering dependency, a filesystem that was not yet mounted, a missing secret, or an environment assumption. Inspect its rc metadata and logs rather than adding a sleep to startup.
Single-user mode: the escape hatch
Booting with -s requests single-user mode and avoids the normal multi-user rc sequence. It is the standard recovery path for a broken /etc/fstab, a password repair, or a service that prevents startup. The root filesystem may initially be read-only, and the PATH and mounted filesystems are intentionally minimal:
/sbin/mount -u -w /
# Inspect /etc/fstab before mounting additional filesystems.
/sbin/mount -a
These commands are not an unconditional recipe. Do not mount filesystems whose configuration is the suspected failure. If UFS requires fsck, keep the target unmounted and follow fsck(8); never check a mounted writable filesystem. ZFS has different recovery semantics from UFS. After repairs, exit lets init continue toward multi-user mode; a controlled shutdown -r now is preferable when boot-critical files or storage topology changed.
A stage-based forensic workflow
A blank screen before any loader output points toward power, firmware, console selection, boot variables, or early bootstrap code. A loader prompt that cannot list the root device points toward disk discovery, partitioning, or filesystem support. A kernel that loads but stops at mountroot> has progressed farther: inspect the device names and root specification rather than reinstalling the ESP.
If PID 1 starts and the machine enters single-user mode, read the actual filesystem-check or mount error. If multi-user startup begins, use rc ordering, console output, and /var/log/messages to isolate the failing service. The visible boundary determines which configuration files can possibly be responsible.
Before modifying bootcode, preserve gpart show -p, gpart backup, loader variables, console photographs, and the exact kernel message. Boot repair is a state-reconstruction exercise. Evidence gathered before a destructive rewrite is often the difference between restoring one component and replacing a valid configuration with the wrong firmware path.
Why the separation matters in practice
The strength of FreeBSD’s sequence is not that boot failures are simple; it is that each transition leaves recognizable evidence. Firmware locates bootstrap code, loader constructs the kernel environment, the kernel owns hardware and root mounting, init owns system state transitions, and rc scripts own ordered userland services. Treating those as separate fault domains turns an alarming failure into a bounded investigation.
Related:
- Fixing ‘Mounting from ufs:/dev/… failed’ at FreeBSD Boot
- Building a Custom FreeBSD Kernel from Source
Sources: