Manual NetBSD Installation with Disk Encryption

Last edited on 2025-04-21 Tagged under  #netbsd   #bsd   #encrypt 

Tested on NetBSD 10.1

The first time I installed NetBSD I used sysinst(8), a menu-based program launched at boot that runs in the console. It has a clear and concise layout and I was quickly up-and-running on my new BSD system.

For my next install I wanted to include disk encryption to protect personal data in case the device is lost or stolen. Its not really enough to simply encrypt home directories. Passphrases and sensitive data can linger and be extracted from locations such as system logs and swap memory. There is a trade-off to be made between how much to encrypt, the convenience of operating the system, and the ability for the system to boot.

NetBSD ascii logo

The arrangement outlined below uses an unencrypted EFI system partition (UEFI) plus a minimal root filesystem that boots up to an encryption passphrase prompt, which upon successful entry unlocks an encrypted partition containing var, usr, and home. A separate swap partition is also created and encryption is automatically enabled at boot using a random key.

Sysinst does not provide the option for encrypting the system in this manner, so early in the install process I switch to the console and proceed to manually install NetBSD.

This is how I do it...



1. Start Here

Throughout this guide, if you see square brackets [] in code blocks, that means the word of code (square brackets included) should be replaced with something else. This is detailed in the instructions before or after the code block.

This guide makes a few assumptions:

  • Target device is amd64 architecture
  • System uses UEFI to boot and will use a GPT partition table
  • NetBSD will be the sole OS on a single disk
  • NetBSD install media will be prepared on a Linux-based system
  • Ethernet will be used for network access during install

1.1 Download install image

Download the NetBSD install image and the SHA512 file for verification:

$ wget -c https://cdn.netbsd.org/pub/NetBSD/NetBSD-10.1/images/NetBSD-10.1-amd64-install.img.gz
$ wget https://cdn.netbsd.org/pub/NetBSD/images/10.1/SHA512

Verify the disk image using sha512sum:

$ sha512sum -c --ignore-missing SHA512 
NetBSD-10.1-amd64-install.img.gz: OK

1.2 Prepare USB install media

Plug in a USB stick and identify its device label:

$ lsblk -f

If the USB stick is mounted, unmount the device:

# umount /dev/[device_label]

For example, if the USB stick is mounted as sde1:

# umount /dev/sde1

Unpack the install image:

# gunzip NetBSD-10.1-amd64-install.img.gz

NOTE
Be very careful to note the proper device. All contents on the device will be lost!

Write the install image to the USB stick using the dd command:

# dd if=NetBSD-10.1-amd64-install.img of=/dev/[device_label] bs=1M status=progress && sync

Using the previous example of sde1, this command would replace [device_label] with sde (omit the partition number):

# dd if=NetBSD-10.1-amd64-install.img of=/dev/sde bs=1M status=progress && sync

2. Configure Live Environment

Insert the USB install stick into the target device and boot. Select Option 1 (default) to install NetBSD.

After the installer has successfully booted into the NetBSD kernel, a prompt appears to select which language will be used for installation messages.

Next is a prompt to select a different keyboard type if desired or leave unchanged.

Next up the menu-based sysinst program is launched:

NetBSD-10.1 Install System

>a: Install NetBSD to hard disk
 b: Upgrade NetBSD on a hard disk
 c: Re-install sets or install additional sets
 d: Reboot the computer
 e: Utility menu
 f: Config menu
 x: Exit Install System

2.1 Configure network

Select >e: Utility menu then >c: Configure network.

Available interfaces lists the network interfaces detected by the NetBSD installer.

EXAMPLE
On my system I have a single (Intel) ethernet interface listed: wm0

Select the ethernet interface for configuration:

  • For Network media type leave empty and indicate Yes for autoconfiguration
  • For host name for the purposes of this HOWTO I enter bsdbox
  • For DNS domain enter home.arpa
Your host name: bsdbox
Your DNS domain [home]: home.arpa

...

Are they OK?

>a: Yes

2.2 Remote login to installer

OPTIONAL
I make this manual install process easier - enable cut-n-paste commands from this HOWTO, access to a web browser to lookup information online - by remotely logging into the installer via ssh(1) from another computer.

Set up ssh on the target device by selecting >a: Run /bin/sh from the Utilities menu.

Create a passwd(1) for root:

# passwd root

Open sshd_config(5) for editing with vi(1):

# vi /etc/ssh/sshd_config

Set permission to allow root to login:

PermitRootLogin yes

Save changes and exit.

Start sshd(8):

# /etc/rc.d/sshd onestart

Retrieve IP address for the active interface configured earlier (example: wm0) with ifconfig(8):

# ifconfig wm0

Switch to the other computer and login to target device via ssh:

$ ssh root@<target_device_ip_address>

3. Prepare disk

EXAMPLE
For the purposes of this HOWTO, the target device has a single NVMe disk with an existing install of Linux that will be erased and replaced by NetBSD. Device IDs and storage sizes will vary between devices.

3.1 Identify disks and partitions

Discover what disk devices and partitions have been recognized by the kernel using sysctl(8):

# sysctl hw.disknames
hw.disknames = ld0 dk0 dk1 dk2 dk3 sd0 dk4 dk5 sd1

NVMe devices show up as ld and hard disks are identified by wd. USB devices usually show up as sd.

The dk devices are partitions (know as wedges in NetBSD parlance) on the storage devices, and this early after boot are usually displayed in order, that is: dk0 through dk3 are wedges on the NVMe target device ld0, and dk4 and dk5 on the USB installer sd0.

Verify by asking for a list of wedges on sd0 using dkctl(8):

# dkctl sd0 listwedges
/dev/rsd0: 2 wedges:
dk4: EFI system, 262144 blocks at 2048, type: msdos
dk5: 30c4cc4e-5369-449c-8994-a4b1ea665b4b, 4853760 blocks at 264192, type: ffs

Check which device the installer booted from using dmesg(8):

# dmesg | fgrep "root on"
[     4.158650] root on dk5

More details about individual disks can be retrieved by extracting parts of the kernel output from the dmesg output (example: for ld0):

# dmesg | fgrep ld0
[     1.045032] ld0 at nvme0 nsid 1
[     1.045032] ld0: 931 GB, 121601 cyl, 255 head, 63 sec, 512 bytes/sect x 1953525168 sectors
[     2.178653] ld0: GPT GUID: 2271c564-dd66-4398-b6f3-12bec0c9df07
[     2.178653] dk0 at ld0: "esp", 614400 blocks at 2048, type: msdos
[     2.248652] dk1 at ld0: "boot", 2097152 blocks at 616448, type: ext2fs
[     2.308652] dk2 at ld0: "root", 134217728 blocks at 2713600, type: <unknown>
[     2.378653] dk3 at ld0: "home", 1816573327 blocks at 136931328, type: <unknown>

There is also the nvmectl(8) command (use atactl(8) for SATA drives):

# nvmectl identify nvme0 | egrep 'Model|Device type|Capacity'
Model Number:               Samsung SSD 980 1TB

3.2 Create wedges

EXAMPLE
Swap partition size is set to >= installed RAM size on target device.

For our new encrypted NetBSD install, create a GPT partition table with four wedges:

SizeFormatUse as
128MBmsdosEFI system partition
6GBffsroot partition
8GBswapswap partition
->ENDcgdencrypted partition

EXAMPLE
The NVMe storage device detected above as ld0 is where NetBSD will be installed. Adjust accordingly for your own storage device.

Set the mydisk variable:

# mydisk="ld0"

Create a new GPT partition table with gpt(8):

# gpt destroy $mydisk
# gpt create -f $mydisk

Create the wedges:

# gpt add -l "ESP" -t efi -s 128m $mydisk
# gpt add -l "root" -t ffs -s 6g $mydisk
# gpt add -l "swap" -t swap -s 8g $mydisk
# gpt add -l "syscgd" -t cgd $mydisk
# gpt show $mydisk

List wedges with dkctl(8):

# dkctl $mydisk listwedges
/dev/rld0: 4 wedges:
dk0: ESP, 262144 blocks at 4096, type: msdos
dk1: root, 12582912 blocks at 266240, type: ffs
dk2: swap, 16777216 blocks at 12849152, type: swap
dk3: syscgd, 1923895296 blocks at 29626368, type: cgd

3.3 Format and mount wedges

Format the EFI wedge with newfs_msdos(8) and mount:

# newfs_msdos /dev/rdk0
# mount /dev/dk0 /mnt

Format the root wedge with newfs(8) and mount:

# newfs -O 2 dk1
# mount /dev/dk1 /targetroot

3.4 Add EFI boot entries

# mkdir -p /mnt/EFI/boot
# cp -v /usr/mdec/*.efi /mnt/EFI/boot
/usr/mdec/bootia32.efi -> /mnt/EFI/boot/bootia32.efi
/usr/mdec/bootx64.efi -> /mnt/EFI/boot/bootx64.efi

Unmount the EFI wedge:

# umount /mnt

4. Disk Encryption

NetBSD uses the cryptographic device driver (CGD) to create and manage encrypted devices.

4.1 Create encrypted device

Using cgdconfig(8), a parameters file is generated that stores the encryption type, key length, and a random password salt for the new cgd(4).

There are a few different encryption ciphers supported. I choose aes-xts with a 512-bit key:

# mkdir -p /targetroot/etc/cgd
# chmod 700 /targetroot/etc/cgd
# cgdconfig -g -V disklabel -o /targetroot/etc/cgd/syscgd aes-xts 512

EXAMPLE
NAME is the label used (example: syscgd) when the wedge for CGD was created earlier.

Create the encrypted device and assign it a passphrase. This passphrase will be used to open the CGD device at boot:

# cgdconfig -V re-enter cgd0 NAME=syscgd /targetroot/etc/cgd/syscgd

4.2 Create disklabels

NOTE
Disklabel(5) c and d have special meaning in NetBSD (addressing the whole disk) and should not be used.

Within the encrypted device, three disklabels are created:

DisklabelMountpointSize
cgd0a/var6GB
cgd0b/usr48GB
cgd0e/home->END

Create the labels using disklabel(8) in interactive mode:

# disklabel -Ii cgd0
Enter '?' for help
...

Create cgd0a:

partition>a
Filesystem type [4.2BSD]: <enter>
Start offset ('x' to start after partition 'x') [0c, 0s, 0M]: <enter>
Partition size ('$' for all remaining) [947594c, 1940672512s, 947594M]: 6G
  a: ...

Create cgd0b:

partition>b
Filesystem type [unused]: 4.2BSD
Start offset ('x' to start after partition 'x') [0c, 0s, 0M]: a
Partition size ('$' for all remaining) [0c, 0s, 0M]: 48G    
  b: ...

Create cgd0e:

partition>e
Filesystem type [unused]: 4.2BSD
Start offset ('x' to start after partition 'x') [0c, 0s, 0M]: b
Partition size ('$' for all remaining) [0c, 0s, 0M]: $
 e: ...

If satisfied with the edits, write the label and quit disklabel:

partition>W
Label disk [n]?y
Label written
partition>Q

4.3 Verify device

Set configuration in target device:

# echo 'cgd0 NAME=syscgd /etc/cgd/syscgd' > /targetroot/etc/cgd/cgd.conf

Close the CGD device:

# cgdconfig -u cgd0

Unlock the CGD device again with the passphrase set earlier:

# cgdconfig cgd0 NAME=syscgd /targetroot/etc/cgd/syscgd

If everything worked, the cgd0 drive is now open and disklabel is visible with disklabel:

# disklabel cgd0

4.4 Format and mount disklabels

# newfs -O 2 cgd0a
# newfs -O 2 cgd0b
# newfs -O 2 cgd0e
# mkdir /targetroot/var /targetroot/usr /targetroot/home
# mount /dev/cgd0a /targetroot/var
# mount /dev/cgd0b /targetroot/usr
# mount /dev/cgd0e /targetroot/home

5. Installation

The new system is composed of sets (collections of packages) installed to the target device. These sets are located in /amd64/binary/sets.

NOTE
Adding flag p to the tar command is important. It ensures that all files preserve their owners and mode.

At a minimum, you must select a kernel and the base and etc sets. Below are the sets I choose to install:

# cd /amd64/binary/sets
# for set in base comp etc games gpufw kern-GENERIC man misc modules tests text xbase xcomp xetc xfont xserver; do
> tar -xvzpf $set.tar.xz -C /targetroot
> done

6. Configuration

Enter chroot on the target device:

# chroot /targetroot

6.1 Create directories

Create the kern and proc directories:

# mkdir kern proc

6.2 Create devices

# cd dev
# sh MAKEDEV all

NOTE
On a previous install, after rebooting the boot process halted with the error message:

/etc/defaults/rc.conf: cannot create /dev/null: read-only file system
/etc/rc: cannot create /dev/null: read-only file system

MAKEDEV had created null but it was incorrectly configured:

-rw-r--r--  1 root  wheel               0 Apr  15 16:57 null

To avoid this error, remove the existing null:

# rm /dev/null

Re-create null with mknod(8):

# mknod -m 0666 -u root -g wheel /dev/null c 2 2
# ls -l null
crw-rw-rw-  1 root  wheel  2, 2 Apr  21 17:07 null

6.3 Create fstab

# cat > /etc/fstab << EOF
NAME=root         /         ffs     rw      1 1
NAME=swap         none      swap    sw,dp		0 0
tmpfs             /tmp      tmpfs   rw,-m=1777,-s=ram%25
kernfs            /kern     kernfs  rw
ptyfs             /dev/pts  ptyfs   rw
procfs            /proc     procfs  rw
tmpfs             /var/shm  tmpfs   rw,-m1777,-sram%25
# encrypted file systems
/dev/cgd0a        /var      ffs     rw      1 2
/dev/cgd0b        /usr      ffs     rw      1 2
/dev/cgd0e        /home     ffs     rw      1 2 
EOF

6.4 Startup configuration

Open rc.conf(5) for editing:

# vi /etc/rc.conf

EXAMPLE
In this example, my hostname was earlier set to bsdbox.home.arpa during network setup, and my wired interface is wm0. Adjust accordingly.

Settings:

# If this is not set to YES, the system will drop into single-user mode.
#
rc_configured=YES

# Add local overrides below.
#
# Wait for CGD to be unlocked before mounting.
critical_filesystems_local="OPTIONAL:/var OPTIONAL:/usr"
dhcpcd=YES
dhcpcd_flags="-qM wm0"
hostname=bsdbox.home.arpa
sshd=YES
ntpdate=YES
wscons=YES
cgd=YES

Save changes and exit.

6.5 Set root password

# passwd root

6.6 Set keyboard

A full list of keyboard mappings and variants can be found in wskbd(4).

Set encoding [type_of_keyboard] in wscons.conf.

Open file for editing:

# vi /etc/wscons.conf

For example, I use the colemak keymap:

encoding us.colemak

Save changes and exit.

6.7 Set timezone

Create a symlink to the appropriate timezone for your localtime:

# ln -sf /usr/share/zoneinfo/[region/<city_or_sub-region] /etc/localtime

For example, set localtime to Canada/Eastern:

# ln -sf /usr/share/zoneinfo/Canada/Eastern /etc/localtime

6.8 Create network interface

The ifconfig.if(5) contains the configuration details for each network interface.

For example, to create an interface file for the wm0 interface that is assigned an IP address via DHCP:

# cat > /etc/ifconfig.wm0 << EOF
up
media autoselect
EOF

6.9 Enable multiple terminals

Set the status of terminals ttyE1-ttyE3 in ttys(5) from off to on.

Open file for editing:

# vi /etc/ttys

This is how it should look:

# name  getty                           type    status          comments
#
console "/usr/libexec/getty Pc"         wsvt25  off secure
constty "/usr/libexec/getty Pc"         wsvt25  on secure
ttyE0   "/usr/libexec/getty Pc"         wsvt25  off secure
ttyE1   "/usr/libexec/getty Pc"         wsvt25  on secure
ttyE2   "/usr/libexec/getty Pc"         wsvt25  on secure
ttyE3   "/usr/libexec/getty Pc"         wsvt25  on secure

Save changes and exit.

7. Finish up

Exit chroot:

# exit

Unmount:

# cd /
# umount /targetroot/home
# umount /targetroot/usr
# umount /targetroot/var
# umount /targetroot

Close CGD device:

# cgdconfig -u cgd0

Reboot:

# shutdown -r now

NOTE
During boot, when prompted for the encryption passphrase - regardless of the keymap set in wscons.conf - enter the correct passphrase using the US QWERTY keymap:

NAME=syscgd's passphrase:

Upon success, boot resumes....

NetBSD/amd64 (bsdbox.home.arpa) (constty)

login: root
Password:

Welcome to NetBSD!

8. Resources

You can like, share, or comment on this post on the Fediverse 💬

Thanks for reading! Read other posts?

» Next: Customize the Login on NetBSD

« Previous: Use SSH keys on NetBSD for Passwordless Logins to Servers