Skip to content
FreeBSDHow-To Published Updated 4 min readViews unavailable

How to Use ZFS Boot Environments with bectl for Safe Upgrades

How to inspect ZFS boot-environment coverage, create and verify a bectl rollback point, activate it safely, and reclaim space afterward.

A FreeBSD boot environment is a bootable ZFS clone managed by bectl(8). It provides fast rollback for files inside the boot-environment dataset structure, but it is not a snapshot of every filesystem and not a backup: /home, /var, /usr/src, package data, or application datasets may be outside it, and every environment still depends on the same pool and boot devices.

Verify support and dataset coverage

Start with the built-in check and inventory:

bectl check
bectl list -a
zfs list -o name,canmount,mountpoint

bectl check exits successfully when the current layout supports boot environments. In bectl list, N means active now, R active on normal reboot, and T selected once for the next boot.

FreeBSD’s traditional installer layout is “shallow”: environments live under zroot/ROOT, while separately mounted datasets such as zroot/home or zroot/usr/src are excluded. A deep layout can place selected datasets under each BE. Never promise an “entire root filesystem” rollback until zfs list shows which mount points are actually inside the environment.

Make an independent backup of data and configuration the BE excludes. Confirm pool health and free space:

zpool status -x
zpool list
bectl list -D

bectl list -D estimates space attributable to each environment if others were destroyed. Copy-on-write creation is fast, but retained old blocks consume increasing space as the live environment changes.

Create a clearly named rollback point

Use a name that records purpose and date:

bectl create pre-upgrade-2026-07-12
bectl list

By default, bectl create clones the currently booted environment. It is not activated automatically. Verify the new entry and preserve the command output in the change record.

The -r option creates a recursive boot environment for layouts that intentionally use subordinate datasets. Do not add it by habit: understand the shallow/deep structure documented in bectl(8) and test how descendants mount at boot.

Optionally mount the new environment for inspection:

mkdir -p /mnt/be-check
bectl mount pre-upgrade-2026-07-12 /mnt/be-check
env ROOT=/mnt/be-check /mnt/be-check/bin/freebsd-version -ku
bectl unmount pre-upgrade-2026-07-12

Avoid modifying the mounted rollback point. If a mount remains busy, find the process rather than forcing unmount immediately.

Perform the change using its current official procedure

Follow the target FreeBSD release’s installation or upgrade instructions. Do not paste an old hardcoded freebsd-update upgrade -r 14.1-RELEASE command: 14.1 is obsolete, and FreeBSD 15 can also use packaged-base deployment paths. The correct mechanism depends on how the host’s base system is managed.

Before reboot, update bootcode when the target release instructions require it, especially before enabling ZFS pool features. A boot environment cannot help if the firmware/loader cannot read the pool. Do not run zpool upgrade merely because a newer kernel supports more features.

Record kernel and userland state after the update steps:

freebsd-version -kru
bectl list

Some update configurations create their own boot environment. Keep names unambiguous and know which environment contains which stage of a multi-reboot upgrade.

Test the new environment and understand activation

Reboot normally and validate kernel/userland versions, network, storage, jails, packages, and the actual application path. Confirm bectl list marks the expected environment N and R.

To make a different environment the persistent default on future boots:

bectl activate pre-upgrade-2026-07-12
bectl list
reboot

To select it only once, use bectl activate -t name. The T flag should appear in the list. A one-time boot is useful for testing because the normal bootfs selection remains unchanged afterward. bectl activate -T name clears temporary-next-boot configuration according to the current manual.

If the active environment cannot boot, use the loader’s Boot Environment menu to select the preserved one. Rehearse this on the console before an emergency. Remote-only hosts need working out-of-band access; a rollback feature hidden behind an inaccessible boot menu is not an operational recovery plan.

Understand what rollback does not reverse

Booting the prior BE changes which root dataset supplies covered files. It does not roll back separately mounted /home, logs, databases, VM disks, ZFS pool configuration, firmware, GPT bootcode, or remote state. An application schema upgraded in an excluded database may be incompatible with older binaries after rollback.

Packages under /usr/local roll back only if that path belongs to the BE. Inspect the dataset layout rather than assuming. Coordinate database migrations with application-native backups and downgrade procedures.

Boot environments also share block failures, pool loss, encryption keys, and administrative destruction. Replicate backups off the pool and test bare-metal recovery separately.

Reclaim space only after acceptance

Once the new environment has survived the defined observation period and backups are verified, list environments and snapshots:

bectl list -a -D

Then destroy only the confirmed obsolete name:

bectl destroy pre-upgrade-2026-07-12

Current bectl destroy does not provide the reassuring interactive confirmation many administrators expect. -F can force unmount and -o can also destroy the origin; both increase risk. Verify active flags, origin relationships, and the exact name before executing.

The safe habit is: inspect coverage, back up excluded data, create and verify the BE, follow current upgrade instructions, test from console, and retain the rollback point until acceptance. That turns bectl into a controlled recovery mechanism instead of a false promise that every system change is reversible.

Related:

Sources:

Comments