useradd prints nothing and exits 0. That silence is not success, because the account it leaves behind has no home directory, a /bin/sh login shell, and a password field locked in /etc/shadow.
I ran each flag on a fresh Ubuntu 24.04 account and read the state back after every one. The flags that carry the account are -m for the home directory, -s for the login shell and -G for the extra group, and the read-back commands below show whether the job finished.
What useradd writes and what it leaves out
An account lives in the passwd and shadow files, and useradd writes those rows from a template it reads at run time rather than from values compiled into the binary. The Linux user administration guide maps the files and the groups around them.
The template is split across the system configuration files. /etc/default/useradd holds the shell, the skeleton directory and the mail spool flag, while /etc/login.defs holds the UID range, the home directory mode and whether each new account gets a group of its own.
Run useradd with -D and no other argument and it prints the first of those files back to you.
useradd -D
SHELL is /bin/sh there rather than /bin/bash. A login shell without readline gives the new user no arrow keys and no tab completion, which reads as a broken account rather than a default.
| Key | Value on Ubuntu | What it decides |
|---|---|---|
| GROUP | 100 | the fallback primary group when no per-user group is made |
| HOME | /home | the base directory, with the account name appended |
| INACTIVE | -1 | no inactivity lock after the password ages out |
| EXPIRE | empty | the account never expires on its own |
| SHELL | /bin/sh | the login shell when -s is left out |
| SKEL | /etc/skel | the files copied into a new home directory |
| CREATE_MAIL_SPOOL | no | whether /var/mail/<name> is created |
| LOG_INIT | yes | reset the lastlog and faillog entries |
CREATE_HOME is absent from that list because it lives in /etc/login.defs instead, and Ubuntu ships that file with the variable commented out, which means the default is off.
I checked that row after a bare useradd and the shell field holds /bin/sh while the home directory it names is absent. So the bare command writes the account row and stops.

Debian and Ubuntu add a second command on top. adduser is a Perl wrapper that calls useradd with the flags you just saw missing, and it prompts for the password and the full name.
The Debian manual page for useradd calls it a low level utility and says administrators should usually use adduser instead. That advice holds on Debian and Ubuntu, though it is the wrong reading on Fedora.
| Point | useradd | adduser |
|---|---|---|
| Ships in | shadow-utils | the adduser package |
| Interface | flags only | prompts, with flags for scripts |
| Creates the home directory | only with -m | by default |
| Sets the login shell | from /etc/default/useradd unless -s is passed | asks |
| Sets the password | never, unless -p is passed | asks |
| On Fedora and RHEL | the command itself | a symbolic link to useradd |
The Fedora side points the other way. adduser there is a symbolic link to useradd, so the interactive prompt never appears.
A script that has to run on both uses useradd with explicit flags, because that name means the same thing on every distribution.
Before you run it
The command needs root, which the sudo command grants, plus a name that clears two sets of rules and any group you name with -G already present.
- sudo or a root shell, because every write lands in /etc
- a username inside the rules shadow-utils enforces
- every group in the -G list already created
- a decision on the home directory and the login shell
A name may hold letters, digits, underscores and dashes, and it may end with a dollar sign. It cannot start with a dash, it cannot be fully numeric, and the ceiling is 256 characters.
A leading dot is legal and worth avoiding, because the home directory inherits the same name and then disappears from a plain ls.
A name that breaks those rules stops the command with exit 19 before anything is written.
The group is the one that catches people out. useradd will not create a group for -G, so a missing name stops the command with exit 6 and leaves no account behind.
sudo groupadd webteam
Groups first, then the account. The groupadd command guide covers the group id and password options if the plain form is not enough.
Create the account
In practice, a single command covers the creation once you name what a usable account needs.
The command that produces a usable account
The group goes first when it is new, then the account.
sudo groupadd webteam
sudo useradd -m -s /bin/bash -G webteam linus
Nothing prints. Reading the account back is what tells you it is finished rather than started.

I read the passwd row back and its fields run left to right. The account name, an x standing in for the password, the numeric UID, the numeric GID of the primary group, and the home directory followed by the login shell.
That x is a pointer rather than a value. The password itself, or the marker that says there is none, lives in /etc/shadow.
| Flag | What it does | What you get without it |
|---|---|---|
| -m | creates the home directory from the skeleton | no home directory at all |
| -s | sets the login shell in the passwd row | SHELL from /etc/default/useradd, which is /bin/sh |
| -G | adds supplementary groups, comma separated | only the primary group |
| -d | sets the home path | HOME plus the account name |
| -c | writes the fifth field, the description | empty |
| -u | sets the numeric UID | the lowest free value above UID_MIN |
Where the home directory comes from
-m does two jobs, and the second one is what makes the account feel finished.
It creates the directory and it copies the skeleton into it. On Ubuntu the skeleton is /etc/skel, and the startup files it holds are the ones a new login reads.
The mode comes from HOME_MODE in /etc/login.defs, which Ubuntu sets to 0750. Without that line the umask would decide instead.

I read the directory back with stat after -m and the mode is 0750, so only the account and its group can enter it. That group is the per-user group useradd created for the account.
-M overrides everything, including a CREATE_HOME turned on in /etc/login.defs. Use it for an account that should exist in the passwd file without a login directory.
sudo useradd -m -d /opt/appsvc -s /usr/sbin/nologin appsvc
sudo useradd -m -k /etc/skel-web myservice
sudo useradd -M -s /bin/bash ephemeral
-d moves the home path and does not create the directory on its own, so it travels with -m. -k points the copy at a different skeleton, and it means nothing unless -m is present.
The shell, the UID, the full name and the expiry
The remaining flags answer the questions that arrive once the account exists.
sudo useradd -m -s /bin/bash -c "Linus Torvalds" -u 1600 linus
sudo useradd -m -e 2027-12-31 trial
sudo useradd -r -s /usr/sbin/nologin metrics
| Flag | Field it fills | What to know |
|---|---|---|
| -s | the shell field of /etc/passwd | /usr/sbin/nologin blocks an interactive login and leaves the account usable for a service |
| -c | the fifth field, a description | display only, no effect on permissions |
| -u | the numeric UID | must be free unless -o is passed |
| -e | the account expiry date | written as YYYY-MM-DD and read as UTC |
| -f | days of grace after the password expires | 0 disables the account the moment the password ages out |
| -r | a system account | takes a UID from the SYS range and skips the home directory |
The UID is the number the kernel uses to decide who owns a file. Leave -u out and useradd hands over the lowest free value above UID_MIN, which Ubuntu sets to 1000.
Expiry arrives in two forms that get mixed up. -e stops the account on a date, while -f decides how long the account survives after the password ages out, which is a different clock.
I created one account with an expiry date and checked it afterwards with chage -l, which reported the account expiry on its own line and left the password lines alone.
The primary group and the -G groups
Every account has exactly one primary group and any number of supplementary groups, and the two flags that set them are one letter apart.
| Flag | Effect | Where it lands |
|---|---|---|
| -g | sets the primary group, by name or by number | the GID field of /etc/passwd |
| -G | adds supplementary groups | the membership list in /etc/group |
Pass neither and USERGROUPS_ENAB in /etc/login.defs decides. Ubuntu sets it to yes, so useradd creates a group with the same name as the account and makes it the primary.
That group is the second entry in the id output, and it is also the group that owns the home directory.
The -G list is comma separated with no spaces after the commas, and every name in it has to be present before the command runs.
sudo useradd -m -s /bin/bash -G webteam,docker,developers linus
Adding a group after the fact is a different command. The guide to adding a user to a group in Linux covers usermod, and finding the user group of a user covers reading the membership back.
Set the password, then read the account state
useradd leaves the password field locked unless you hand it an encrypted value, and that state is deliberate rather than a defect.
The manual page spells it out. Without -p the new account is locked, with a single exclamation mark in the password field of /etc/shadow.
That marker is not a password and cannot be typed in as one. Every authentication attempt fails against it, so it is a state to check rather than a fault to chase.
sudo getent shadow linus

The second field of the passwd -S line carries the status letter, and that letter is the one to read.
| Letter | Meaning | What to do |
|---|---|---|
| L | locked, with no usable password | run passwd to set one |
| P | a usable password is set | nothing |
| NP | no password at all | set one, because some channels accept a blank password |
I read the shadow entry before setting a password and the field holds that single exclamation mark. Setting the password is its own command, and it asks twice so a typo cannot lock the account away from its own password.
sudo passwd linus
The characters you type stay off the screen, and I drove the whole exchange through an interactive terminal session so the captured frame is the session rather than a retyped copy of it. Expiring the password is a third command, and it turns a handed-over credential into one that has to be replaced at first login.
sudo passwd --expire linus
sudo chage -l linus
chage -l then reports the last change as password must be changed until the account holder does it. Reading the account back takes a few commands, and each one answers a different question.
- getent passwd linus reports what the account row holds, including the home path and the shell
- id linus reports the UID, the primary group, and every supplementary group
- sudo passwd -S linus reports whether there is a usable password
The passwd command guide covers the locking and aging options, and listing all users in Ubuntu covers reading the whole account list back.
When useradd refuses
A handful of mistakes cover nearly every failure, and each one stops the command before it writes anything.

| Message | Exit | Cause | Fix |
|---|---|---|---|
| user ‘linus’ already exists | 9 | the name is taken in the passwd file or a directory service | pick another name, or repair the account that holds it |
| UID 1002 is not unique | 4 | -u named a number already in use | leave -u out, or pass -o when the duplicate is deliberate |
| group ‘nosuchteam’ does not exist | 6 | -G or -g named a group that is not there | create the group first |
The useradd manual page lists every exit code, and the ones worth knowing are 1 for a password file that could not be written, 2 for a syntax error, 3 for a bad argument, 10 for the group file, 12 for the home directory, 14 for SELinux, and 19 for a name that breaks the rules.
Exit 12 is the one to recognise in a provisioning script, because it means the account row was written and the home directory was not. An unwritable parent directory or a wrong SELinux context on the parent produces it.
The failures that print nothing are the ones worth guarding against, and the account from the first section is the example. It exists, it exits 0, and it cannot be logged into.
Repairing one is a two step job, because the flags that would have prevented it do not all work after the fact.
sudo usermod -s /bin/bash deploy
sudo mkhomedir_helper deploy

usermod -s rewrites the shell field. The same command with -d and -m moves an existing home directory and does not create a missing one, which is why the second line is mkhomedir_helper instead.
mkhomedir_helper comes from the PAM packages, copies the skeleton, and leaves the mode at 0755 against the 0750 that HOME_MODE gives useradd -m. I ran both on the same half-built account and compared the two modes, and the difference held.
Deleting and recreating is the other repair, and it costs you the UID and everything the old account already owned.
A name that a directory service also holds is refused outright. Accounts kept in NIS or LDAP have to be created on the server that holds them.
Remove the account later
userdel takes the row back out, and -r decides whether the files go with it.
sudo userdel linus
sudo userdel -r linus

The mail spool line is a report rather than a failure. CREATE_MAIL_SPOOL is set to no in /etc/default/useradd, so the file was never created, and userdel reports that rather than passing over it.
I ran userdel -r against an account whose home directory had never been created and got both warnings on one command, with an exit status of 0.
A logged-in account blocks its own removal, so end the sessions first.
loginctl terminate-user linus
sudo userdel -r linus
loginctl is the clean route where systemd is running. Killing the account’s own processes is the route that occasionally takes down the shell that typed the command.
The UID goes back into the pool the moment the account is gone, so the next account can be handed the same number.
Files the deleted account owned keep the old numeric owner, and a new account with the recycled UID inherits them. That is how a fresh account ends up owning another person’s files.
The next command to run
The check that closes the job costs one command, and it catches the failure this page opened with. I ran the same read-back on the demo account and it carries the UID, the primary group and the supplementary group on one line.
id linus
A UID, a primary group, a supplementary group for every name you passed to -G, and a home path you have confirmed exists on disk. Anything missing there means the account is half-built, and the login failure will arrive later and somewhere else.
That line belongs at the end of any provisioning script. A command that prints nothing and exits 0 is only trustworthy once something else has looked at the result.
FAQ
What does the useradd command do in Linux?
It writes the account rows in /etc/passwd, /etc/shadow and /etc/group using the defaults in /etc/default/useradd and /etc/login.defs. It writes only what the flags name, so a bare run leaves no home directory and a /bin/sh shell.
Why did useradd not create a home directory?
CREATE_HOME is commented out in /etc/login.defs on Ubuntu, so the default is off. Pass -m to create the directory and copy /etc/skel into it.
What is the difference between adduser and useradd?
On Debian and Ubuntu, adduser is a Perl wrapper that calls useradd with the missing flags and prompts for the password. On Fedora and RHEL the same name is a symbolic link to useradd, so explicit useradd flags are the portable choice.
How do I set the password for a user created with useradd?
Run sudo passwd followed by the account name. useradd leaves the account locked with an exclamation mark in /etc/shadow, and passwd -S shows that state as L until a password is set.
How do I add a user to a group with useradd?
Pass -G with a comma separated list of groups and create every one of them first. Use -g for the primary group instead, which is a different field with a different effect.
What does exit code 9 from useradd mean?
The username or group name is already in use. Nothing was written, so pick another name or repair the account that already holds it.
