Add, lock and remove users, learn the difference between usermod -G and -S, write doas rules and check them before you install them. Every example run for real, with the errors you will meet.
▶Prefer to watch? This short video runs this page's main commands on a real OpenBSD 8.0 machine, with on-screen captions and a synthetic voiceover (an AI voice trained on the author's own recording). Click to play, or watch it on YouTube.
doas useradd -m -c "Ann Example" ann # new user with a home
doas usermod -S web ann # set the groups exactly
doas -C /tmp/new.conf id # check a doas rule first
OpenBSD manages users with a few small commands that edit plain files, has doas instead of sudo, lets only the group wheel use su, and mails a nightly report of changes. This page adds, locks and removes users, shows the two ways to change groups, writes doas rules and the mistake that makes the later rule win, tries su outside wheel and reads the security report.
Every example ran for real on OpenBSD 8.0 (amd64) in a virtual machine, with root commands run through doas. The output you see is the output the machine gave.
Users and groups, and where they live
OpenBSD keeps its users in plain files and gives you a few small commands to edit them. If you know Linux, the commands will look familiar. The files are much the same too, with the real password hashes in a file that only root can read.
Every example here ran for real on OpenBSD 8.0 (amd64) in a virtual machine, as an ordinary user, with doas in front of the commands that need root. First, who are we, and what do the files hold?
$ id
uid=1000(bsd) gid=1000(bsd) groups=1000(bsd), 0(wheel)
$ grep '^bsd:' /etc/passwd
bsd:*:1000:1000:Practice User:/home/bsd:/bin/ksh
$ doas grep '^bsd:' /etc/master.passwd | cut -c1-24
bsd:$2b$11$07qFFQWx9LFEl
$ ls -l /etc/passwd /etc/master.passwd /etc/spwd.db /etc/pwd.db
-rw------- 1 root wheel 4370 Nov 4 20:55 /etc/master.passwd
-rw-r--r-- 1 root wheel 3873 Nov 4 20:55 /etc/passwd
-rw-r--r-- 1 root wheel 40960 Nov 4 20:55 /etc/pwd.db
-rw-r----- 1 root _shadow 40960 Nov 4 20:55 /etc/spwd.db
$ grep -E '^(wheel|staff|users):' /etc/group
wheel:*:0:root,bsd
users:*:10:
staff:*:20:root
/etc/passwd can be read by everyone and has a * where the hash would be. The real hash is in /etc/master.passwd, readable by root only. The two .db files are the same data in a form the system can look up fast, and they are rebuilt for you by the user commands. The group wheel is the group of administrators, and our user is in it.
Adding a user
useradd creates the account, and with -m it makes the home directory and copies the files in /etc/skel into it. The comment is the full name:
$ doas useradd -m -c "Ann Example" ann
$ id ann
uid=1001(ann) gid=1001(ann) groups=1001(ann)
$ grep '^ann:' /etc/passwd
ann:*:1001:1001:Ann Example:/home/ann:/bin/ksh
$ ls -a /home/ann
.
..
.Xdefaults
.cshrc
.cvsrc
.login
.mailrc
.profile
.ssh
$ doas userinfo ann
login ann
passwd *
uid 1001
groups ann
change NEVER
class
gecos Ann Example
dir /home/ann
shell /bin/ksh
expire NEVER
The new user got the next free number, 1001, a group of the same name, the shell /bin/ksh, and a password field with a single *, which means nobody can log in with a password yet. If you leave out -m, useradd warns you that the home directory does not exist and goes on:
$ doas useradd nohome
useradd: Warning: home directory `/home/nohome' doesn't exist, and -m was not specified
$ doas userdel nohome
Passwords, locking and shells
To set a password, run doas passwd ann and type it twice, or give usermod a ready-made hash with -p. The OpenBSD command that makes the hash is encrypt, and it uses bcrypt, with a new random salt every time:
$ echo hunter2 | encrypt | cut -c1-12
$2b$09$LZtgt
$ echo hunter2 | encrypt | cut -c1-12
$2b$09$icOec
$ doas usermod -p "$(echo hunter2 | encrypt)" ann
$ doas grep '^ann:' /etc/master.passwd | cut -c1-30
ann:$2b$09$KI75HVDtxhbw/DgS7Nu
OpenBSD has no lock option. To lock an account, replace the hash with a string that can never be a hash, such as a row of stars. The password is lost that way, so to unlock you set a new one:
$ doas usermod -p '*************' ann
$ doas grep '^ann:' /etc/master.passwd | cut -c1-30
ann:*************:1001:1001::0
$ doas usermod -p "$(echo hunter2 | encrypt)" ann
An account that should never log in at all gets the shell /sbin/nologin:
$ doas usermod -s /sbin/nologin ann
$ doas su ann -c id; echo "exit status $?"
This account is currently not available.
exit status 1
$ doas usermod -s /bin/ksh ann
Groups
Groups are made with groupadd, and a user is put in them with usermod. There are two options and the difference matters. -G appends to the groups the user has. -S sets the list, and so it is also the way to take a user out of a group:
$ doas groupadd -g 2000 web
$ doas usermod -G web ann
$ id ann
uid=1001(ann) gid=1001(ann) groups=1001(ann), 2000(web)
$ doas usermod -G wheel ann
$ id ann
uid=1001(ann) gid=1001(ann) groups=1001(ann), 0(wheel), 2000(web)
Both groups are there, because -G only adds. To remove wheel again, set the whole list:
$ doas usermod -S web ann
$ id ann
uid=1001(ann) gid=1001(ann) groups=1001(ann), 2000(web)
$ doas groupinfo web
name web
passwd *
gid 2000
members ann
An empty list, -S '', removes the user from all the secondary groups. Compare this with NetBSD, where there is no such option and you have to edit /etc/group.
doas
OpenBSD has no sudo. The base system has doas, a much smaller program that reads a short rules file, /etc/doas.conf, which only root can read. Here is the one in this machine:
$ doas cat /etc/doas.conf
permit nopass bsd
permit nopass keepenv root
Each line is a rule. permit or deny comes first, then options such as nopass (no password) or persist (ask the password once for a while), then who: a user name, or :group for a group. After that come as USER, cmd COMMAND and args to narrow the rule down. The most important thing to know is that the last rule that matches decides. Before you install a file, ask doas what it would do with it. doas -C reads a file and says what the current user would be allowed to run. This file has the general rule last, and it goes wrong:
$ cat /tmp/trap.conf
permit nopass bsd
permit persist :wheel
$ doas -C /tmp/trap.conf id
permit
The answer is only permit, with no nopass, so doas would ask for a password. Our user is also in wheel, and the later line won. If you installed that file, a script that runs doas without a terminal could not do anything. Put the general rule first, and the specific ones after it:
$ cat /tmp/new.conf
permit persist :wheel
permit nopass bsd
permit nopass keepenv root
permit nopass ann as root cmd rcctl args get httpd flags
deny ann as root cmd rm
$ doas -C /tmp/new.conf id
permit nopass
$ doas -C /tmp/new.conf rm
permit nopass
Now it says permit nopass, as intended. Keep a copy of the old file, install the new one, and try the rules for Ann. She cannot log in yet, so the test runs a second doas as her. She may read one setting of httpd as root, and nothing else:
$ doas cp /etc/doas.conf /etc/doas.conf.orig
$ doas cp /tmp/new.conf /etc/doas.conf
$ doas -u ann doas rcctl get httpd flags
NO
$ doas -u ann doas rcctl get sshd flags; echo "exit status $?"
doas: Operation not permitted
exit status 1
$ doas -u ann doas rm /tmp/x; echo "exit status $?"
doas: Operation not permitted
exit status 1
The first works. The other two fail with the same words, Operation not permitted. The deny line is not even needed for the last one, because nothing permits it. It is there to make the intention clear.
su and wheel
On OpenBSD only members of the group wheel can become root with su, even if they know the root password. Try it as a user who is not in the group. The check comes before the password is asked, so it works without a terminal:
$ doas useradd -m dan
$ doas -u dan su -; echo "exit status $?"
you are not in group wheel
Sorry
exit status 1
Remember that the way to give a user su is to add the group, as in the last section, with usermod -G wheel. For one or two administrators, su and wheel may be all you need, and doas is for the rest.
Removing users
To remove a user, use userdel. By default it leaves the home directory behind, so that files are not lost by accident, and -r removes it too:
$ doas userdel dan
$ ls /home
ann
bsd
dan
$ doas userdel -r ann
$ ls /home
bsd
dan
$ id ann
id: ann: No such user
(exit status 1)
Note that userdel does not look for files the user owns outside the home directory. They stay with the old number, and a new user who gets the same number would own them. Find them first, with find / -user NAME -ls, before you delete the account.
The security report and securelevel
OpenBSD checks itself every night. The daily job runs /usr/libexec/security, which compares the important files with the copy it made the last time, and mails the differences to root. The same checks can be run by hand. The first run on a new system is long, because it reports everything changed since the install. It also saves a new copy of each file, so the next run only has news in it. Run it once and throw the output away, then make one change and run it again:
$ doas /usr/libexec/security > /dev/null 2>&1
(exit status 1)
$ doas useradd -m erin
$ doas /usr/libexec/security 2>&1 | grep -A9 '^/etc/group diffs'
/etc/group diffs (-OLD +NEW)
======
--- /var/backups/etc_group.current Fri Nov 6 19:20:56 2026
+++ /etc/group Fri Nov 6 19:20:57 2026
@@ -93,3 +93,4 @@
nohome:*:1002:
web:*:2000:
dan:*:1003:
+erin:*:1004:
$ doas /usr/libexec/security 2>&1 | grep -A1 '^Login erin'
Login erin is off but still has a valid shell and alternate access files in
home directory are still readable.
The new line for erin is shown with a +. The same report also warns about an account whose password cannot work but whose shell can, which is what a new account without a password is. An unexpected account in that report is worth a look.
OpenBSD also has the securelevel that NetBSD has, but you do not have to switch it on. It is already 1 whenever the system runs in multi-user mode:
$ sysctl kern.securelevel
kern.securelevel=1
At level 1, immutable file flags cannot be cleared, and the memory devices and the kernel cannot be changed by root. Only a reboot to single-user mode brings the level down. It is one more reason why the machine is hard to damage from the inside.
When it goes wrong
The messages are short, and they mean what they say. Creating a user or a group that already exists, or removing one that does not:
$ doas useradd erin; echo "exit status $?"
useradd: already a `erin' user
exit status 1
$ doas groupadd web; echo "exit status $?"
groupadd: group `web' already exists
groupadd: can't add group: problems with /etc/group file
exit status 1
$ doas userdel nosuch; echo "exit status $?"
userdel: No such user `nosuch'
exit status 1
A rules file with a mistake. doas -C finds it before you install it, and tells you the line. A rule that ends right after cmd is not complete:
$ cat /tmp/bad.conf
permit nopass bsd
permit ann as root cmd
$ doas -C /tmp/bad.conf; echo "exit status $?"
doas: syntax error at line 3
exit status 1
And a user who has no rule, which gets only Operation not permitted, because doas does not say why. Here it is the system user daemon:
$ doas -u daemon doas id; echo "exit status $?"
doas: Operation not permitted
exit status 1
When doas says a tty is required, a rule that needs a password matched, and there was nowhere to type it. Look at the order of the rules, as above: the last one that matches wins.