If you manage Linux servers, sooner or later you have to add a human being (or a service) to the system. That’s what useradd is for. It’s the low-level, no-hand-holding tool that every distribution ships to create new user accounts, and once you understand what it actually does under the hood, user management on Linux stops feeling like guesswork.
I’ve used useradd on everything from a single Raspberry Pi to fleets of production Ubuntu and RHEL boxes, and in this guide I’m going to walk through it the way I wish someone had walked me through it the first time — starting from the absolute basics and going all the way to the internals of what happens on disk when you run it.
What useradd Actually Is
useradd is a low-level utility, part of the shadow-utils (on RHEL/Fedora) or passwd (on Debian/Ubuntu) package, used to create new user accounts on a Linux system. It’s written in C and lives at /usr/sbin/useradd. Because it’s a system binary rather than a shell script, it directly edits the core account databases: /etc/passwd, /etc/shadow, /etc/group, and /etc/gshadow.
It’s worth contrasting useradd with adduser. On Debian-based systems, adduser is a friendlier Perl wrapper around useradd that asks interactive questions and sets sane defaults automatically. useradd, on the other hand, is deliberately minimal — by default, if you just type useradd john, you’ll get an account, but without a password set and, depending on the distro, without a home directory. This is intentional: useradd is meant to be scriptable and predictable, not chatty.
Basic Syntax
useradd [options] LOGIN
useradd -D
useradd -D [options]
The first form creates a new account named LOGIN. The -D form is special — it shows or changes the default values used when new users are created (I’ll cover that separately below).
You need root privileges (or sudo) to run useradd, since it writes to protected files like /etc/shadow.
A Minimal Example
sudo useradd john
Run this and check what happened:
grep john /etc/passwd
john:x:1001:1001::/home/john:/bin/sh
Notice a few things already:
- UID and GID were auto-assigned (1001 here, following on from the last regular user).
- The home directory field shows
/home/john, but on many distros the directory itself is not created unless you pass-m. - The shell defaults to
/bin/sh, not necessarily/bin/bash. - The account has no password yet — it’s locked until you set one with
passwd john.
This is the classic “gotcha” for people coming from adduser: useradd alone gives you a bare-bones entry, not a fully working login.
Full Option Reference
Here is the actual set of options as reported by useradd --help on a modern shadow-utils build:
Usage: useradd [options] LOGIN
useradd -D
useradd -D [options]
Options:
--badname do not check for bad names
-b, --base-dir BASE_DIR base directory for the home directory of the
new account
--btrfs-subvolume-home use BTRFS subvolume for home directory
-c, --comment COMMENT GECOS field of the new account
-d, --home-dir HOME_DIR home directory of the new account
-D, --defaults print or change default useradd configuration
-e, --expiredate EXPIRE_DATE expiration date of the new account
-f, --inactive INACTIVE password inactivity period of the new account
-F, --add-subids-for-system add entries to sub[ud]id even when adding a system user
-g, --gid GROUP name or ID of the primary group of the new
account
-G, --groups GROUPS list of supplementary groups of the new
account
-h, --help display this help message and exit
-k, --skel SKEL_DIR use this alternative skeleton directory
-K, --key KEY=VALUE override /etc/login.defs defaults
-l, --no-log-init do not add the user to the lastlog and
faillog databases
-m, --create-home create the user's home directory
-M, --no-create-home do not create the user's home directory
-N, --no-user-group do not create a group with the same name as
the user
-o, --non-unique allow to create users with duplicate
(non-unique) UID
-p, --password PASSWORD encrypted password of the new account
-r, --system create a system account
-R, --root CHROOT_DIR directory to chroot into
-P, --prefix PREFIX_DIR prefix directory where are located the /etc/* files
-s, --shell SHELL login shell of the new account
-u, --uid UID user ID of the new account
-U, --user-group create a group with the same name as the user
-Z, --selinux-user SEUSER use a specific SEUSER for the SELinux user mapping
--extrausers Use the extra users database
Let’s break down the ones you’ll actually use day to day.
-m / --create-home
This is the option I almost always add. Without it, no home directory is created — the user has a home path listed in /etc/passwd, but the directory doesn’t physically exist, so on first login they’ll have nowhere to put files (and some shells will complain).
sudo useradd -m john
When -m is used, useradd copies the contents of the skeleton directory (/etc/skel by default) into the new home directory — that’s where .bashrc, .bash_profile, .profile, etc. come from.
-d / --home-dir
Overrides the default home directory path (normally /home/<username>):
sudo useradd -m -d /srv/apps/john john
-s / --shell
Sets the login shell. Default varies by distro — Debian/Ubuntu often defaults to /bin/sh, RHEL-based systems often default to /bin/bash. Always check /etc/default/useradd or /etc/login.defs.
sudo useradd -m -s /bin/bash john
To create an account that can’t log in interactively (common for service accounts):
sudo useradd -m -s /usr/sbin/nologin appuser
-u / --uid
Manually assigns a UID instead of letting the system pick the next free one:
sudo useradd -u 1500 -m john
Useful when syncing UIDs across NFS-mounted systems, containers, or when matching a UID from another server.
-g / --gid and -G / --groups
-g sets the primary group (by name or GID). -G sets supplementary groups (comma-separated, no spaces):
sudo useradd -m -g developers -G sudo,docker,www-data john
If -g is omitted, most modern distros create a private group matching the username (User Private Group scheme) — this is controlled by USERGROUPS_ENAB in /etc/login.defs.
-c / --comment
Sets the GECOS field — traditionally used for the user’s full name, and shown by tools like finger:
sudo useradd -m -c "John Carter, DevOps" john
-e / --expiredate and -f / --inactive
-e sets an account expiration date (YYYY-MM-DD) — after this date the account is disabled entirely. -f sets how many days after a password expires the account gets locked:
sudo useradd -m -e 2026-12-31 -f 7 contractor
This is standard practice for contractor or temporary accounts — set it once at creation and never worry about manually disabling it later.
-r / --system
Creates a system account: UID is picked from the system range (below UID_MIN, typically under 1000), no expiration by default, and often no home directory unless -m is explicit. Used for daemons and services.
sudo useradd -r -s /usr/sbin/nologin -m -d /var/lib/myapp myappsvc
-p / --password
Sets an already-encrypted password hash — not a plaintext one. If you pass plaintext here, it gets stored as-is (broken and insecure), so this is almost always misused by beginners. The safe pattern:
sudo useradd -m john
sudo passwd john
Or, for scripting, generate a hash properly and pass it:
HASH=$(openssl passwd -6 'StrongPassword123!')
sudo useradd -m -p "$HASH" john
-N / --no-user-group and -U / --user-group
Controls whether a private group matching the username is created. -U forces it, -N suppresses it (falling back to a shared default group, usually users).
-o / --non-unique
Allows creating a user with a duplicate UID — two usernames sharing one UID. Rare, but used for aliasing accounts.
-k / --skel
Points to an alternate skeleton directory instead of /etc/skel, useful when different account types need different default dotfiles.
sudo useradd -m -k /etc/skel-devs -s /bin/bash devuser
Checking and Changing Defaults with -D
Running useradd -D alone prints current defaults:
useradd -D
GROUP=100
HOME=/home
INACTIVE=-1
EXPIRE=
SHELL=/bin/sh
SKEL=/etc/skel
CREATE_MAIL_SPOOL=no
These values live in /etc/default/useradd. You can change them permanently:
sudo useradd -D -s /bin/bash
This rewrites SHELL=/bin/bash in /etc/default/useradd, so every future useradd call without -s gets bash by default.
What Files Change When You Run useradd
This is the part that separates people who “use the command” from people who actually understand Linux account internals.
/etc/passwd — one line per user: username:x:UID:GID:GECOS:home:shell. The x means the real password hash lives elsewhere (in shadow), not here — this file is world-readable.
/etc/shadow — password hashes and aging data, readable only by root: username:hash:lastchange:min:max:warn:inactive:expire:.
/etc/group and /etc/gshadow — group membership and group password data.
/etc/login.defs — system-wide defaults for UID/GID ranges (UID_MIN, UID_MAX, SYS_UID_MIN, etc.), password aging policy, and whether useradd creates a mail spool.
/etc/default/useradd — defaults consumed specifically by useradd (home base dir, default shell, skeleton dir).
Internally, useradd doesn’t edit these files directly with a text editor — it uses locking (/etc/passwd.lock, etc.) to avoid corrupting the database if two processes try to modify it simultaneously, then writes atomically and releases the lock. This is why you should never hand-edit /etc/passwd while useradd/usermod/userdel might be running concurrently — use vipw and vigr if you must edit by hand.
Real-World System Administration Examples
Creating a standard developer account:
sudo useradd -m -s /bin/bash -c "Jane Doe" -G sudo,docker jane
sudo passwd jane
Creating a service account for an application with no login shell and a custom home:
sudo useradd -r -m -d /opt/myapp -s /usr/sbin/nologin -c "MyApp service account" myapp
Bulk-creating users from a list (shell scripting example):
#!/bin/bash
# create_users.sh - bulk create users from a text file (one username per line)
while IFS= read -r username; do
if id "$username" &>/dev/null; then
echo "User $username already exists, skipping."
else
useradd -m -s /bin/bash "$username"
echo "${username}:ChangeMe123!" | chpasswd
passwd -e "$username" # force password change on first login
echo "Created user: $username"
fi
done < users.txt
Creating a user with a matching UID/GID for NFS consistency:
sudo groupadd -g 2001 sharedgroup
sudo useradd -m -u 2001 -g 2001 nfsuser
How This Compares to Related Tools
adduser(Debian/Ubuntu) — interactive, friendlier wrapper arounduseraddthat also prompts for a password and full name. Good for manual admin work; less good for scripting because of its interactivity.usermod— modifies an existing account (change shell, add to groups, rename, lock/unlock).userdel— removes an account (userdel -ralso removes the home directory and mail spool).chpasswd— batch-sets passwords for many users at once fromusername:passwordpairs, useful in the scripting example above.pwck/grpck— sanity-check/etc/passwd//etc/shadowand/etc/group//etc/gshadowfor consistency after manual edits.
useradd is consistent across RHEL, CentOS, Fedora, Debian, Ubuntu, SUSE, and Arch — it’s part of the POSIX-adjacent shadow-utils toolset, so scripts written against it are highly portable. The main differences you’ll hit across distros are the defaults (default shell, whether a UID-matching private group is created, whether home directories are auto-created), not the command’s behavior itself.
Troubleshooting Common Issues
“useradd: user ‘john’ already exists” — check with id john or getent passwd john; if you need to change something, use usermod, not useradd.
Home directory not created — you forgot -m. Check with ls -ld /home/john.
User can log in via SSH key but shell immediately exits — shell is set to /usr/sbin/nologin or /bin/false; check with getent passwd john and fix with usermod -s /bin/bash john.
“useradd: UID 1000 is not unique” — you tried to reuse a UID without -o. Either pick a different UID or add --non-unique if that’s genuinely intended.
Account created but can’t log in with a password — the account is locked (password field starts with ! in /etc/shadow) until you run passwd username.
Security Considerations
- Never pass plaintext passwords via
-p; always use pre-hashed values or set the password separately withpasswd/chpasswd. - For service accounts, always pair
-rwith-s /usr/sbin/nologin(or/bin/false) to prevent interactive login. - Be deliberate with UID ranges — mixing system and regular UID spaces can create confusing permission bugs, especially with containers that map UIDs across namespaces.
- Set
-e(expiration dates) for temporary or contractor accounts rather than relying on manual cleanup later. - Audit
/etc/passwdand/etc/shadowperiodically withpwckto catch inconsistencies, especially after scripted bulk account creation.
Summary
useradd is deliberately minimal, predictable, and script-friendly — it does exactly what you tell it and nothing more, which is precisely why it’s the right tool for automation and configuration management (Ansible, Puppet, Bash provisioning scripts all lean on it directly or indirectly). Once you understand that it’s really just a careful, locking-aware editor for /etc/passwd, /etc/shadow, /etc/group, and /etc/gshadow, the whole Linux user-management model stops being mysterious.
References
- GNU/Linux
shadow-utilsproject documentation:man useradd,man login.defs,man 5 passwd,man 5 shadow - Debian Administrator’s Handbook, chapter on user and group management
- Red Hat Enterprise Linux System Administrator’s Guide, “Managing Users and Groups”
/etc/login.defsand/etc/default/useraddman pages on your local system for distro-specific defaults
