Mastodon

FreeBSD Foundationals: The Boot Process - From the Loader to Boot Environments



The two previous articles in this series covered the things that store your data and the things that run your workloads. This one covers the part that nobody thinks about until it’s three in the morning and the machine won’t come back up: how FreeBSD actually boots, and - more importantly - how to make a bad boot recoverable.

This is the third article in the FreeBSD Foundationals series. The first covered Jails, the second covered ZFS. We’re covering the boot process now because everything else depends on it and because, compared to other UNIX-like systems, FreeBSD keeps the early boot chain unusually small and inspectable. There’s a small, documented chain of stages, a text file you edit by hand, and - if you’re on root-on-ZFS - a recovery mechanism that is genuinely one of the best reasons to run FreeBSD on a server.

By the end of this article you’ll understand what each boot stage does, where to put a setting and why, the difference between a tunable and a sysctl (people get this wrong constantly), the modern way to load kernel modules, and how boot environments turn “I bricked it during an upgrade” into a non-event.

Table of Contents

The Boot Chain: Power-On to Login

FreeBSD boots in stages. Each stage has exactly one job: find and start the next, slightly smarter, stage. The chain differs at the very bottom depending on whether your firmware is legacy BIOS or UEFI, but it converges quickly on the same component - the loader - and from there the path is identical.

Legacy BIOS (MBR/GPT)              UEFI

┌──────────────────┐              ┌──────────────────┐
│   BIOS firmware  │              │  UEFI firmware   │
└────────┬─────────┘              └────────┬─────────┘
         │                                 │ reads the ESP
   boot0 / boot1                           │ (FAT partition)
   boot2 / gptzfsboot                      │
         │                                 │
         └──────────────┬──────────────────┘
                        │ loads
                ┌───────┴────────┐
                │  /boot/loader  │   ← stage 3: the loader
                │ (loader.efi on │     reads loader.conf,
                │     UEFI)      │     loads kernel + modules
                └───────┬────────┘
                        │
                ┌───────┴────────┐
                │  kernel        │   ← mounts root, starts init
                └───────┬────────┘
                        │
                ┌───────┴────────┐
                │  init(8)       │   ← runs /etc/rc
                └───────┬────────┘
                        │
                ┌───────┴────────┐
                │  rc(8) scripts │   ← rc.conf, services, jails
                └───────┬────────┘
                        │
                     login

On legacy BIOS, the firmware reads the first sector of the boot disk (the MBR, containing boot0), which chains to a partition’s boot1/boot2 (or gptboot on GPT, or gptzfsboot when root is on ZFS). These tiny programs exist only to understand just enough of the filesystem to find and load the loader. They are measured in hundreds of bytes for a reason: there isn’t much room in a boot sector.

On UEFI, all of that disappears. The firmware itself understands the FAT-formatted EFI System Partition (ESP) and loads an EFI executable directly - for FreeBSD that’s loader.efi, installed at /EFI/freebsd/loader.efi and usually also as the fallback /EFI/BOOT/BOOTX64.EFI. No boot0, no boot2, no chain of sectors. UEFI is generally the default for modern installs and simplifies the boot chain on most hardware.

Either way, you land at the loader, and that’s where things get interesting.

Where Does ZFS Fit In?

If your root filesystem is on ZFS - which, after the previous article, it should be - the early boot code needs to understand ZFS well enough to find the bootfs: the dataset marked as the one to boot from. On BIOS this is the job of gptzfsboot; on UEFI, loader.efi has ZFS support built in. The loader reads the pool, finds the active boot environment, loads the kernel from it, and hands off. Hold onto the phrase “active boot environment” - it’s the whole reason this article exists.

The Loader: Stage Three and the Last Friendly Face

The loader (/boot/loader, or loader.efi under UEFI) is the last component before the kernel takes over, and it’s the most capable. It’s a small interactive environment - scripted primarily in Forth, with optional Lua support available in modern FreeBSD builds - that:

  • Shows you the boot menu (the orb menu with the options)
  • Reads /boot/loader.conf for configuration
  • Loads the kernel and any kernel modules you’ve requested (early KLDs only - once the kernel starts, module management shifts to the rc system and kld(4))
  • Sets boot-time tunables (more on these shortly)
  • Lets you drop to an interactive prompt to fix a system that won’t boot

That last point is worth dwelling on. When the boot menu appears, pressing the right key (the menu lists them) lets you boot single-user, select a different kernel, choose a boot environment, or escape to the loader prompt entirely. At the prompt you can inspect and change anything before the kernel starts:

OK show                  # show all loader variables
OK lsdev                 # list devices the loader can see
OK set vfs.root.mountfrom=zfs:zroot/ROOT/default
OK unload                # unload the default kernel
OK load /boot/kernel.old/kernel   # load a known-good kernel instead
OK boot -s               # boot into single-user mode

This is the FreeBSD equivalent of a GRUB rescue prompt, except documented, consistent, and pleasant to use.

loader.conf: What Goes Here and Why

/boot/loader.conf is the loader’s configuration file. The defaults live in /boot/defaults/loader.conf (read it once to see what’s available - don’t edit it); your overrides go in /boot/loader.conf. There’s also /boot/loader.conf.local for layered configs (handy if a config-management tool owns loader.conf and you want machine-specific additions).

Two distinct kinds of things go in loader.conf:

  1. Module load directives - something_load="YES", which tells the loader to load something.ko before the kernel starts.
  2. Boot-time tunables - kernel variables that can only be set before the kernel boots and are read-only afterward.

Here’s a real loader.conf from one of my jail hosts, annotated:

# Boot-time tunables: read-only once the kernel is running.
# These MUST live in loader.conf - you cannot set them later.
kern.geom.label.disk_ident.enable="0"   # don't clutter /dev with diskid/ labels
kern.geom.label.gptid.enable="0"        # prefer stable gpt/ labels over gptids
net.fibs="2"                            # enable a second routing table (FIB)
kern.racct.enable="1"                   # turn on resource accounting (see RCTL article)

# Modules loaded early by the loader
cryptodev_load="YES"
zfs_load="YES"                          # required: root is on ZFS
pf_load="YES"
virtio_random_load="YES"                # early entropy on a VM

# Filesystem modules
nullfs_load="YES"
fdescfs_load="YES"

# Network modules for VNET jails
if_epair_load="YES"
if_bridge_load="YES"
if_tuntap_load="YES"

Every line here works. But - and this is one of the main points of the article - a good portion of it can often be moved elsewhere, depending on intent. We’ll come back to that in the modules section.

A few more loader.conf knobs worth knowing, taken from a bhyve host where boot speed and virtualization matter:

# Boot menu / speed
autoboot_delay="2"          # seconds to wait at the menu (default 10; "-1" waits forever, "NO" skips)
hw.usb.no_boot_wait="1"     # don't stall boot probing USB devices

# bhyve virtualization
vmm_load="YES"              # the bhyve hypervisor module
nmdm_load="YES"             # null-modem devices for VM serial consoles

# Asynchronous I/O - recommended for ZFS and database workloads
aio_load="YES"

# Load a TCP congestion-control algorithm so it can be selected at runtime
cc_bbr_load="YES"

That last line is a perfect illustration of the loader/sysctl split, so let’s make it explicit. Loading cc_bbr makes the BBR congestion-control algorithm available. It does not make anything use it. Selecting it is a runtime sysctl:

# In /etc/sysctl.conf - selects the algorithm the module made available
net.inet.tcp.cc.algorithm=bbr

The module has to be loaded (loader.conf or kld_list), and then the choice is a sysctl. Get this relationship clear in your head and a lot of FreeBSD tuning stops being mysterious.

Before we go deeper, here’s the whole configuration model on one screen - keep this map in mind as the rest of the article fills in each piece:

loader.conf  →  early boot constraints (boot-time tunables + root-critical modules)
sysctl.conf  →  runtime kernel behavior
rc.conf      →  system services (and kld_list)
kld_list     →  late module availability (after root is mounted)
bectl        →  rollback boundary (undo a bad change)

Tunables vs Sysctls: The Distinction Everyone Gets Wrong

This trips up nearly everyone coming from Linux, where sysctl.conf is the one place you put kernel knobs. FreeBSD splits kernel parameters into two categories, and putting one in the wrong file means it either silently does nothing or refuses to take effect.

Tunable Sysctl (runtime)
Set where /boot/loader.conf /etc/sysctl.conf
Set when Before the kernel boots Any time, including at boot
Changeable at runtime Typically not (often read-only once set) Yes - sysctl name=value
Examples net.fibs, kern.racct.enable, kern.geom.label.* net.inet.tcp.blackhole, security.bsd.see_other_uids

The rule of thumb: some kernel values are baked in at boot because changing them later would be unsafe or meaningless (the number of routing tables, whether resource accounting is compiled into the running kernel’s behavior, geometry labelling). Those are tunables and only loader.conf can set them. Everything that’s safe to change on a running system is a sysctl and belongs in sysctl.conf.

There’s a dead-simple diagnostic to tell which is which. Try to set it at runtime:

# If this works, it's a runtime sysctl -> put it in /etc/sysctl.conf
sysctl net.inet.tcp.blackhole=2

# If you get "oid is read only", it's a boot-time tunable -> put it in /boot/loader.conf
sysctl net.fibs=2
# sysctl: oid 'net.fibs' is read only

You can also ask sysctl to describe any value, which is great when you’re not sure what a knob does:

sysctl -d security.bsd.see_other_uids
# security.bsd.see_other_uids: Unprivileged processes may see subjects/objects with different real uid

sysctl.conf: Where Runtime Knobs Live

/etc/sysctl.conf is read late in boot and piped through sysctl to apply runtime values. The format is bare name=value, one per line (no quoting, unlike loader.conf). To apply changes without rebooting:

# Re-apply the whole file (idempotent for writable values)
service sysctl restart
# ...which is just a wrapper around:
sysctl -f /etc/sysctl.conf

If a value is read-only and you accidentally put it here, you’ll see the “oid is read only” error at boot and in the service sysctl restart output - a useful signal that it belongs in loader.conf instead.

Loading Kernel Modules: The Modern Way Is kld_list

Now back to that claim about moving modules out of loader.conf. There are two ways to load a kernel module at boot, and the right choice depends on when the module is actually needed.

module_load="YES" in loader.conf loads the module in stage three, before the kernel starts. Use this only for modules that must be present that early:

  • The filesystem driver for your root (zfs on root-on-ZFS).
  • Disk encryption that wraps the root (geom_eli / aesni for a GELI-encrypted root).
  • Anything the loader or early kernel genuinely needs before root is mounted.

kld_list in /etc/rc.conf loads modules during the rc startup sequence, right after the disks are mounted but before services start. This is the modern, preferred way for everything that doesn’t need to exist at loader time. It keeps loader.conf minimal, keeps the loader fast, and loads modules with the fully running kernel rather than the constrained loader environment.

The test is simple: does the module need to be loaded before root is mounted? If no, it belongs in kld_list. Almost all network-interface modules, nullfs, fdescfs, pf, vmm, and similar fall into “no.”

Look again at the jail host’s loader.conf from earlier. The if_epair, if_bridge, if_tuntap, nullfs, and fdescfs modules are only needed by the time jails start - long after root is mounted. They have no business slowing down the loader. Here’s the same machine, modernized:

# /boot/loader.conf - only what the loader genuinely needs early
# Boot-time tunables (read-only after boot)
kern.geom.label.disk_ident.enable="0"
kern.geom.label.gptid.enable="0"
net.fibs="2"
kern.racct.enable="1"

# Modules required before/at root mount
zfs_load="YES"
cryptodev_load="YES"
virtio_random_load="YES"   # early entropy on a VM
# /etc/rc.conf - modules that only need to exist by the time services start
kld_list="nullfs fdescfs if_epair if_bridge if_tuntap"

Note that pf dropped off the list entirely. You don’t need pf_load or pf in kld_list: the pf rc script (enabled with pf_enable="YES") initializes the firewall and, in most standard configurations, loads the module if it isn’t already built in. (See the PF firewall guide for the full firewall story.) Fewer redundant knobs, same result.

The payoff: a lean loader.conf that boots fast and only contains things that truly must be early, and a kld_list that’s easy to read and lives alongside the rest of your service configuration in rc.conf. When you add a new VNET jail and realize you need another interface module, you add it to kld_list - not to the loader.

One naming note: kld_list uses module base names only (e.g. if_epair, not if_epair.ko or the loader.conf-style if_epair_load variables).

sysrc: Stop Hand-Editing rc.conf

/etc/rc.conf controls what the system does after it’s booted - which services run, the hostname, networking, jails, and (as we just saw) kld_list. You can edit it with vi. You shouldn’t, most of the time, because sysrc(8) does it safely, idempotently, and scriptably.

# Read a value
sysrc sshd_enable
# sshd_enable: YES

# Set a value (adds it if missing, replaces it if present)
sysrc sshd_enable=YES

# Append to a list-valued variable without clobbering the rest
sysrc kld_list+="if_lagg"

# Remove a single element from a list
sysrc kld_list-="if_tuntap"

# Delete a variable entirely
sysrc -x moused_enable

# Show everything that's been set (excluding defaults)
sysrc -a

The += and -= operators are the reason to use sysrc even if you’re comfortable in an editor. Appending one module to kld_list without having to retype the whole quoted string - and without risking a typo that breaks every module on the line - is exactly the kind of small safety that adds up across dozens of machines.

sysrc isn’t limited to rc.conf. The -f flag points it at any rc-style file, including loader.conf:

# Edit loader.conf safely from a script
sysrc -f /boot/loader.conf zfs_load=YES

# Read a loader tunable
sysrc -f /boot/loader.conf net.fibs

This makes sysrc a natural fit for automation. In the cdist and Ansible world, idempotent sysrc calls are far cleaner than templating the whole file and far safer than line-based edits. It either ends in the desired state or tells you why it couldn’t.

A Practical Security-Hardening Baseline

Boot configuration is where a lot of host hardening actually lives, because the strongest knobs are kernel-level and set exactly once at boot. Here’s a baseline I apply to internet-facing FreeBSD hosts, split correctly between loader.conf (tunables) and sysctl.conf (runtime). Treat it as a starting point, not gospel - understand each line before you ship it.

loader.conf hardening

# Refuse the dangerous, system-modifying DTrace actions outright
security.bsd.allow_destructive_dtrace="0"

allow_destructive_dtrace is a boot-time tunable: DTrace’s destructive actions (which can stop processes, modify memory, raise signals) are something you decide about before the kernel runs, not after. On a production box that isn’t actively being debugged with destructive probes, set it to 0.

sysctl.conf hardening

# --- Process and information isolation ---
security.bsd.see_other_uids=0          # users can't see other users' processes
security.bsd.see_other_gids=0
security.bsd.see_jail_proc=0           # host root can't see into jail process tables
security.bsd.unprivileged_read_msgbuf=0  # hide the kernel message buffer (dmesg)
security.bsd.unprivileged_proc_debug=0   # forbid ptrace/debugging for unprivileged users
kern.randompid=1                       # randomize PIDs

# --- Hardlink restrictions (prevent some symlink/hardlink attacks) ---
security.bsd.hardlink_check_uid=1
security.bsd.hardlink_check_gid=1
security.bsd.unprivileged_mlock=0

# --- Address-space layout randomization ---
kern.elf64.aslr.enable=1
kern.elf32.aslr.enable=1

# --- Network stealth: drop, don't reject, on closed ports ---
net.inet.tcp.blackhole=2               # closed TCP ports send no RST
net.inet.udp.blackhole=1               # closed UDP ports send no ICMP unreachable

A few of these deserve a sentence:

  • see_jail_proc=0 is part of the jail isolation story from the first article: it stops even host root from casually listing processes inside jails, narrowing the blast radius of a compromised host account.
  • blackhole reduces the active feedback a host gives during a port scan - a probe against a closed port gets silence instead of a helpful RST or ICMP message. It slows down reconnaissance, though packet timing and other behavior can still reveal that the host is alive. It does not replace a firewall; it complements one.
  • ASLR (kern.elf*.aslr.enable=1) randomizes the memory layout of executables, raising the bar for memory-corruption exploits. It’s generally enabled by default on modern FreeBSD releases, but worth verifying explicitly.

Don’t filter bridge traffic twice

If you run VNET jails on a bridge, there’s a subtle pair of sysctls worth setting deliberately rather than inheriting by accident:

# Keep packets crossing the bridge from being filtered on the host's pf,
# unless you specifically intend to run pf on the bridge itself.
net.link.bridge.pfil_bridge=0
net.link.bridge.pfil_member=0

These are 0 by default, but stating them in sysctl.conf documents the intent: filtering happens inside each jail’s own pf, not redundantly on the host bridge. This connects directly to the bridge-and-epair model from the Jails article.

CPU mitigations and entropy

CPU side-channel mitigations (MDS, etc.) are largely automatic on current FreeBSD, but you can inspect and pin them. On older Intel hardware where you want to be explicit:

# Automatic MDS mitigation selection (0=off, 3=auto)
hw.mds_disable=3

Check what the running kernel actually decided with the machdep.mitigations sysctl tree:

sysctl machdep.mitigations

A note on routers

The hardening above is for general hosts. A box doing a specific job loads specific modules. My BGP edge routers, for instance, carry a loader.conf that’s all about crypto and TCP-MD5:

aesni_load="YES"            # AES-NI hardware acceleration
crypto_load="YES"
cryptodev_load="YES"
cc_htcp_load="YES"          # H-TCP congestion control
tcpmd5_load="YES"           # TCP-MD5 signatures for BGP sessions (RFC 2385)
ipsec_load="YES"
virtio_random_load="YES"

tcpmd5 and ipsec are exactly the kind of modules you might keep in loader.conf: they relate to the network stack’s security posture and there’s an argument for having them present as early as possible. The point isn’t that one file is always right - it’s that you should be able to justify, for each module, why it’s where it is. If you can’t, it probably belongs in kld_list. (The whole BGP build-out is covered in the running your own AS series if you want the routing context.)

Boot Environments: The Undo Button

This is the part that makes the whole article worth reading. If you run root-on-ZFS, you have access to boot environments, and they change the risk calculus of every system upgrade you will ever do.

A boot environment (BE) is a bootable clone of your root filesystem. Recall from the ZFS article that a clone is a writable snapshot that costs essentially nothing until it diverges. FreeBSD’s root-on-ZFS layout is designed to support this pattern:

zroot
├── ROOT
│   └── default        ← this is the active boot environment (your "/")
├── usr
│   ├── home
│   └── ...
├── var
│   └── ...
└── tmp

Everything under zroot/ROOT/<name> is a candidate root filesystem. The loader reads which one is marked active (the bootfs) and boots it. bectl(8) is the tool that manages them, and the loader’s boot menu can switch between them interactively.

The mental model: a boot environment is a complete, independent copy of your operating system root that you can switch between at the loader menu. Your data (/usr/home, /var, etc.) lives in separate datasets and is shared across BEs - only the OS root differs. That’s the magic: rolling back the OS doesn’t roll back your data.

The Workflow That Saves You

Here’s the entire point, in five lines:

# Before any risky change: take a named boot environment
bectl create before-14.2-upgrade

# Do the scary thing
freebsd-update upgrade -r 14.2-RELEASE
freebsd-update install
reboot

If the upgrade went fine, you’re done - the new system is running and before-14.2-upgrade sits there as a free safety net you can destroy later. If the upgrade went badly - kernel panic, broken userland, a service that won’t start, a box that won’t even reach multi-user - you don’t restore from backup and you don’t reinstall. You reboot, and at the loader’s Boot Environments menu, you select before-14.2-upgrade. Thirty seconds later you’re running the exact system you had before you started, down to the last byte.

That’s the undo button. A failed upgrade stops being an outage and becomes an inconvenience.

bectl in Practice

# List boot environments (Active "N" = active now, "R" = active on Reboot)
bectl list
BE                  Active Mountpoint Space Created
default             NR     /          12.4G 2026-01-04 09:12
before-14.2-upgrade -      -          1.20G 2026-06-25 21:40
# Create a BE from the current root
bectl create before-14.2-upgrade

# Create a BE from a specific point - e.g. an existing snapshot
bectl create -e default@clean before-experiment

# Activate a BE for the NEXT boot (persistent until changed)
bectl activate before-14.2-upgrade

# Activate for ONE boot only - falls back to the previous BE afterward
bectl activate -t before-14.2-upgrade

# Mount a BE to inspect or copy files out of it without booting it
bectl mount before-14.2-upgrade /mnt
bectl umount before-14.2-upgrade

# Rename and destroy
bectl rename before-experiment kept-for-reference
bectl destroy before-14.2-upgrade

The Active column is the one to read carefully. N means now (the BE you’re currently running). R means reboot (the BE that will be active next boot). NR together means the running BE is also the one set for next boot - the normal steady state. After bectl activate other, you’ll see N on your current BE and R on other, which is your cue that a reboot will switch you.

Boot Environments and Upgrades

The single best habit you can build on a FreeBSD server: bectl create before every upgrade. Not just OS upgrades - package upgrades too. pkg upgrade can pull in a broken dependency or a config change that takes down a service, and pkg has no transactional rollback mechanism comparable to boot environments. A boot environment is your rollback:

bectl create before-pkg-$(date +%Y%m%d)
pkg upgrade
# ...if something breaks and you can't fix it forward, reboot into the BE

freebsd-update on recent FreeBSD can integrate with boot environments directly, and some shops wire bectl create into their package automation so every change is automatically reversible. Even without automation, a single manual bectl create costs you nothing and a few seconds, and one day it will save you an evening.

The Temporary-Activation Trick

bectl activate -t is underused and worth knowing. It activates a BE for exactly one boot. If that boot succeeds and you’re happy, you can make it permanent; if the machine doesn’t come back (and you have console/IPMI to reboot it), the next boot automatically reverts to the previously active BE. This is the safest possible way to test a new BE on a remote machine: worst case, a power cycle puts you back on known-good.

When the Boot Goes Wrong: Single-User and the Loader Prompt

Boot environments cover the “a change broke the system” case. For the “I need to fix something with the filesystem barely mounted” case, there’s single-user mode.

From the boot menu, choose single-user, or from the loader prompt:

OK boot -s

Single-user drops you to a root shell with only the root filesystem mounted (often read-only, depending on configuration) and no services running. It’s where you go to:

  • Fix a broken /etc/rc.conf that’s killing the boot
  • Run fsck on a UFS filesystem (not needed for ZFS)
  • Reset a forgotten root password
  • Repair a loader.conf that loads a panicking module

Typical single-user repair session:

# Remount root read-write
mount -uw /

# Mount the rest
zfs mount -a        # on root-on-ZFS
# or: mount -a       # on UFS

# Fix the offending file, then continue booting to multi-user
exit

For one-off kernel selection without editing anything, nextboot(8) sets the kernel (or other loader variables) for only the next boot:

# Boot kernel.old exactly once on the next reboot
nextboot -k kernel.old
reboot

Between bectl, single-user mode, nextboot, and the loader prompt’s load/unload/boot, there is almost no boot failure you can’t recover from in-place - provided you can reach a console. Which leads neatly to the pitfalls.

Common Pitfalls

Putting a tunable in sysctl.conf. Setting net.fibs=2 in /etc/sysctl.conf does nothing useful - it’s read-only at runtime, so you get an “oid is read only” error at boot and the second FIB never appears. Boot-time tunables go in loader.conf, full stop. When in doubt, try sysctl name=value at runtime: if it errors read-only, it’s a tunable.

Loading everything in loader.conf. It works, but it bloats the loader and obscures what’s actually essential to early boot. Reserve loader.conf for tunables and root-critical modules; put the rest in kld_list. Your future self, scanning the file at 3 AM, will thank you.

No console access on a remote box. Boot environments and single-user mode are only useful if you can reboot and reach the loader menu or a serial console. On a rented server, this means IPMI/KVM or a provider rescue system. Set this up before you need it, not during the outage. Test that you can actually reach the loader menu remotely.

Forgetting bectl create before upgrades. The entire safety net is opt-in. A BE you didn’t create can’t save you. Build the habit until it’s reflexive: no freebsd-update install and no pkg upgrade on a server without a fresh BE first.

Assuming boot environments roll back your data. They don’t, and that’s by design. A BE clones the OS root, not /var or /usr/home. If an upgrade corrupted a database in /var/db, booting an old BE gives you the old binaries but the same damaged data. For data, you still want ZFS snapshots and off-site replication (the syncoid setup from the ZFS article). BEs and snapshots are complementary, not redundant.

Editing /boot/defaults/loader.conf. That file is overwritten on upgrade. Put overrides in /boot/loader.conf (or loader.conf.local). Same logic as never editing /etc/defaults/rc.conf.

Conclusion

FreeBSD’s boot path is short, documented, and - crucially - recoverable. A handful of staged loaders hands off to the loader, the loader reads a plain text file and loads your kernel, the kernel mounts root and runs rc, and you’re at a login prompt. Every layer is inspectable and every layer is fixable from a console.

The mental model to carry away: loader.conf is for things the loader must do before the kernel exists - boot-time tunables and root-critical modules. sysctl.conf is for runtime kernel knobs. kld_list in rc.conf is the modern home for every module that can wait until after root is mounted, and sysrc is how you edit all of it safely. And boot environments - the single best reason to put your root on ZFS - turn the scariest operation a sysadmin performs, the upgrade, into something you can undo at the boot menu in under a minute.

If you take four habits from this article: keep loader.conf minimal and move non-essential modules to kld_list; know the tunable-vs-sysctl distinction cold; harden at the kernel level with a reviewed sysctl.conf baseline; and run bectl create before every single upgrade. That last one will, eventually, be the best thirty seconds you ever spent.

The next article in this series tackles the other thing Linux users consistently get wrong about FreeBSD: the base system versus ports and packages - where all the software actually comes from, why base and third-party software are deliberately separate worlds, and how freebsd-update, pkg, and poudriere fit together.


References

Comments

You can use your Mastodon or other ActivityPub account to comment on this article by replying to the associated post.

Search for the copied link on your Mastodon instance to reply.

Loading comments...