How the OpenBSD installer creates new users?
Published on: 18 November 2025
During the installation of OpenBSD you get asked if you want to set up a new user or not. Usually I like to have a non-root user, but one time I didn't create it because I wanted to do some quick tests but then I wondered: how are new users set up during installation? Which permissions does they get, which groups do they join, and so on?
Thankfully OpenBSD code is very well structured and the installer is
only around 2400 lines of shell code available at
distrib/miniroot/install.sub, so it didn't took me too long to find
out the part of the code where the user is created. Here is the code
snippet.
# Create user account based on information from user_setup().
if [[ -n $ADMIN ]]; then
_encr=$(encr_pwd "$ADMIN_PASS")
_home=/home/$ADMIN
uline="${ADMIN}:${_encr}:1000:1000:staff:0:0:${ADMIN_NAME}:$_home:/bin/ksh"
echo "$uline" >>/mnt/etc/master.passwd
echo "${ADMIN}:*:1000:" >>/mnt/etc/group
echo $ADMIN >/mnt/root/.forward
_home=/mnt$_home
mkdir -p $_home
(cd /mnt/etc/skel; pax -rw -k -pe . $_home)
(umask 077 && sed "s,^To: root\$,To: ${ADMIN_NAME} <${ADMIN}>," \
/mnt/var/mail/root >/mnt/var/mail/$ADMIN )
chown -R 1000:1000 $_home /mnt/var/mail/$ADMIN
sed -i -e "s@^wheel:.:0:root\$@wheel:\*:0:root,${ADMIN}@" \
/mnt/etc/group 2>/dev/null
# During autoinstall, add public ssh key to authorized_keys.
[[ -n "$ADMIN_KEY" ]] &&
print -r -- "$ADMIN_KEY" >>$_home/.ssh/authorized_keys
fi
Let's analyze it together. The password is generated using the
encr_pwd() function which in our case simply calls encrypt(1) with
the arguments "-b a" which according to the man page encrypts the
string using Blowish hashing with the number of rounds depending of
how capable is the machine CPU.
Once encrypted it generates a string that is going to be used for the
/etc/master.passwd file. We can find the structure of the file in
the man page passwd(5).
name User's login name. password User's encrypted password. uid User's login user ID. gid User's login group ID. class User's general classification (see login.conf(5)). change Password change time. expire Account expiration time. gecos General information about the user. home_dir User's home directory. shell User's login shell.
In OpenBSD the /etc/passwd is public-readable and it is generated by
pwd_mkdb(8) from /etc/paster.passwd with some fields stripped out.
Next it is creating a new group for the user, and the format is in the
man page group(5).
group Name of the group. passwd Group's encrypted password. gid The group's decimal ID. member Group members.
It then creates a file .forward in the root home directory, and I
didn't know what this meant. Thankfully the man pages came handy here
is as well forward(5).
Users may put a .forward file in their home directory. If this file exists, smtpd(8) forwards email to the destinations specified therein.
It makes total sense that the mails for the root should be forwarded to the main user.
After that the home directory is created and the default skeleton of the directory is copied over and the proper permissions are set.
Finally the user is added to the wheel group, which means that only
the user can run su to become root. This is explained in the su(1)
man page.
If group 0 (normally "wheel") has users listed then only those users can su to "root". It is not sufficient to change a user's /etc/passwd entry to add them to the "wheel" group; they must explicitly be listed in /etc/group. If no one is in the "wheel" group, it is ignored, and anyone who knows the root password is permitted to su to "root".
The installer also creates a welcome email for the new user, but we can ignore that for our use case.
Now, how can I reproduce this as similar as possible if you haven't done it during the installation process? This is the shell script that I created that reproduces the same steps.
#!/bin/ksh
ADMIN_USER=alice
ADMIN_NAME=Alice
ADMIN_PASS=s3curep@ssword
PASSWORD_ENCRYPTED=$(encrypt -b a -- "${ADMIN_PASS}")
groupadd -g 1000 "${ADMIN_USER}"
useradd -p "${PASSWORD_ENCRYPTED}" -u 1000 -g "${ADMIN_USER}" -G wheel -L "staff" -c "${ADMIN_NAME}" -s /bin/ksh -m "${ADMIN_USER}"
This script does not create the .forward file because it's not
needed in most of my cases, but this is a quick reminder in case you
care about it.