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.