Here's the answer that my particular blend of ChatGPT gives to your inquiry. I hope it helps at least to recruit human commentators to help you. And if ChatGPT's answer is itself helpful, good.
A partially read-only FreeBSD system
A partially read-only FreeBSD system is easiest to reason about if the tree is split into three classes:
base system files,
third-party installed files, and
runtime state. On current FreeBSD, the base system lives outside
/usr/local; third-party software installed by
pkg(8)() or the Ports framework lives under
/usr/local; and the paths that are expected to change during normal operation are concentrated under
/var,
/tmp, and user home directories.
hier(7)()
The base system is primarily this set of installed paths:
/boot,
/bin,
/sbin,
/lib,
/libexec,
/etc, and the base portions of
/usr such as
/usr/bin,
/usr/sbin,
/usr/lib,
/usr/libexec, and
/usr/share. The
hier(7)() manual also states that
/libexec contains system utilities critical to binaries in
/bin and
/sbin, while the
/usr/libexec subtree contains system daemons and utilities executed by programs.
Third-party software belongs under
/usr/local. In current
hier(7)(),
/usr/local is explicitly described as the location for local executables, libraries, and related files installed by
pkg(7)() or
ports(7)(). That same hierarchy defines
/usr/local/etc as local program configuration,
/usr/local/lib as local libraries, and
/usr/local/libexec as utilities executed by local utilities. The Handbook also states the general configuration split directly: base configuration in
/etc, third-party application configuration in
/usr/local/etc.
So the
libexec split is this:
- /libexec: base-system helpers critical to programs in /bin and /sbin.
- /usr/libexec: base-system daemons and helper programs executed by other base programs.
- /usr/local/libexec: the third-party counterpart for helpers executed by locally installed software.
The kernel and kernel modules are stored under
/boot. The normal module paths are
/boot/kernel and
/boot/modules. Those directories are mostly read-only in day-to-day operation, but they are not completely exempt from boot-time writes: if
kldxref_enable="YES", FreeBSD rebuilds
linker.hints files at boot in the module paths.
Hazard: A strictly read-only /boot can break boot-time module metadata updates. If /boot must remain read-only after provisioning, set kldxref_enable="NO" only after confirming that module metadata is already correct.
The main runtime-write area is
/var. Current
hier(7)() describes
/var as the location for log, temporary, transient, and spool files, and it explicitly includes
/var/cache/pkg for cached packages. In practice, the important writable subtrees for a normal host are
/var/run,
/var/log,
/var/db,
/var/cache,
/var/tmp, and sometimes
/var/crash.
For package management specifically,
pkg.conf(5)() gives the defaults clearly: the package database is
/var/db/pkg, the package cache is
/var/cache/pkg, and the system-wide
pkg configuration file defaults to
/usr/local/etc/pkg.conf. The Handbook also notes that many systems do not need a
pkg.conf file at all, which is useful when trying to keep configuration minimal.
That is why the statement “almost everything modified is under
/home or
/usr/local” is close, but not complete. A FreeBSD system that actually boots and runs services normally still expects writable state under
/var, writable temporary storage under
/tmp or memory-backed
/tmp, and user-writable homes under
/home.
There are also a few boot-time or first-start writes that surprise people. On current FreeBSD,
update_motd="YES" updates
/var/run/motd, not
/etc/motd. The first time
sshd starts, FreeBSD generates the host keys automatically, which writes under
/etc/ssh. Kernel crash dumps are another case: the dump is written to the configured dump device, and the saved crash data is associated with
/var/crash during processing after reboot.
A practical partially read-only layout therefore looks like this:
Code:
/ read-only
/boot read-only
/usr read-only
/usr/local read-write (or read-only except during package maintenance)
/var read-write
/home read-write
/tmp read-write or memory-backed
Hazard: This layout works only if /usr/local is its own mount point when /usr is read-only. Otherwise /usr/local inherits the read-only state of /usr.
That layout is sound only if
/usr/local is its
own mount point when
/usr is read-only. The FreeBSD Handbook defines a mount point as a directory where another filesystem is grafted onto its parent. When a filesystem is mounted on a directory, the mounted filesystem replaces what was visible there from the parent. So
/usr cannot be mounted read-only while
/usr/local remains independently writable unless
/usr/local is a separate filesystem or dataset mounted on that directory.
For
/tmp, current
rc.conf(5)() provides
tmpmfs, which creates a memory-backed
/tmp using
mdmfs(8)(). It is worth being exact here:
tmpmfs is not the same knob as mounting
tmpfs(5)() manually.
tmpmfs="YES" creates
/tmp as an MFS via
mdmfs, and it can also be triggered automatically if
/tmp is not writable. There is a matching
varmfs mechanism for
/var, but that is usually for more appliance-like or diskless designs because
/var often needs persistent state.
A sane starting policy for a machine that still installs packages locally is this:
- Keep /, /boot, and /usr read-only.
- Keep /var, /tmp, and /home writable.
- Keep /usr/local writable if the machine runs
pkg install or pkg upgrade locally.
- Keep /etc read-only during normal operation only after initial provisioning is complete, especially after SSH host keys already exist.
A more locked-down policy is also possible:
- Keep /usr/local read-only in steady state.
- Remount /usr/local read-write only during package maintenance.
- Keep
kldxref_enable="NO" if /boot stays read-only after boot.
- Keep
update_motd="NO" if the goal is to avoid even /var/run/motd churn.
Hazard: Making /usr/local read-only in steady state prevents package installs, upgrades, and many post-install actions until it is remounted read-write. This affects the host or dataset mounted there.
Hazard: Disabling crash dumps with dumpdev="NO" removes a useful diagnostic path after kernel panics. This affects host-level debugging and incident analysis.
A compact
rc.conf(5)() baseline for this kind of host is:
Code:
# /etc/rc.conf
root_rw_mount="NO"
tmpmfs="YES"
update_motd="NO"
kldxref_enable="NO"
# Optional on hosts where crash dumps are not wanted
dumpdev="NO"
That keeps the root from being remounted read-write during boot, enables a memory-backed
/tmp, avoids the
motd update, and prevents boot-time rebuilding of
linker.hints. The
dumpdev="NO" line is a policy choice, not a requirement.
These checks show whether the layout matches the intended policy:
Code:
# mount
# sysrc root_rw_mount tmpmfs update_motd kldxref_enable dumpdev
# pkg config PKG_DBDIR
# pkg config PKG_CACHEDIR
# ls -ld /var /var/run /var/log /var/db /var/cache/pkg /tmp /var/tmp /home /usr/local
# service sshd status
Those commands verify the mount state, the boot-policy variables, the
pkg metadata paths, and the writable runtime trees.
The shortest accurate summary is this:
- Kernel and modules: mostly /boot/kernel and /boot/modules; watch
kldxref_enable.
- Base system: /boot, /bin, /sbin, /lib, /libexec, /etc, /usr/bin, /usr/sbin, /usr/lib, /usr/libexec, /usr/share.
- Third-party software: /usr/local/**, with config in /usr/local/etc and helper binaries in /usr/local/libexec.
- Package metadata and cache: /var/db/pkg and /var/cache/pkg.
- Normal runtime writes: /var/**, /tmp, /var/tmp, /home/**.
- Common surprise writes: /var/run/motd, /etc/ssh on first
sshd start, and linker.hints if kldxref_enable="YES".
So the robust answer is not “make everything read-only except
/home and
/usr/local.” The robust answer is:
make the installed software trees read-only, keep the runtime-state trees writable, and isolate /usr/local as its own mount point if it must remain writable while /usr is read-only.