| Tested on | RHEL 10.2 (Coughlan) |
|---|---|
| Package | shadow-utils 4.15.0-11.el10 |
| Applies to | RHEL, Rocky Linux, AlmaLinux, Oracle Linux, CentOS Stream, Fedora, Ubuntu, Debian |
| Privilege | sudo or root |
| Scope | Local Linux user and group lifecycle—create, modify, delete, membership, defaults, and file ownership checks. Does not cover LDAP, SSSD, or password aging policy depth. |
| Related guides | useradd command usermod command groupadd command add user to group chage command |
Local accounts still matter on every Linux host: service identities, break-glass admins, lab VMs, and edge nodes that do not use central directory services. This guide walks through the full lifecycle with one consistent example set—a project group projops and two users labalice and labbob—so each command builds on the previous step.
RHCSA-style work centers on useradd, usermod, userdel, groupadd, groupmod, and groupdel, verified with id and getent. Per-command flag references sit in the dedicated useradd, usermod, and groupadd pages; listing patterns appear in the inspect section once the lab accounts exist.
How Linux User and Group Accounts Work
Linux identifies accounts with numeric IDs and human-readable names:
| Concept | Stored in | Meaning |
|---|---|---|
| UID | /etc/passwd field 3 |
Numeric user ID |
| GID | /etc/passwd field 4 |
Primary group ID for new files |
| Username | /etc/passwd field 1 |
Login name |
| Group name | /etc/group field 1 |
Maps to GID |
Primary group — the GID in /etc/passwd. The primary group becomes the process's default group. New files normally use that group, except in locations such as setgid directories where new files inherit the directory's group. umask controls permission bits, not group ownership.
Supplementary groups — extra memberships in /etc/group. A user can belong to many supplementary groups; id lists them after the primary gid=.
Core local files:
| File | Purpose |
|---|---|
/etc/passwd |
Usernames, UIDs, home, shell — world-readable |
/etc/shadow |
Password hashes and aging metadata — root-only |
/etc/group |
Group names, GIDs, member lists |
/etc/gshadow |
Group passwords (rare on modern systems) — root-only |
Never paste live password hashes from /etc/shadow into documentation or tickets. Examples below use passwd -S for password state instead of printing shadow fields.
Create a Local User
Create the project group first so you can assign a shared primary or supplementary GID:
sudo groupadd projopsgroupadd exits silently when the name is new. Confirm the group exists and note its GID before you attach users:
getent group projopsprojops:x:1002:GID 1002 is what useradd -g or usermod -G will reference in this lab.
Add labalice with projops as the primary group, a home directory, login shell, and GECOS comment:
sudo useradd -m -c 'Alice Ops' -s /bin/bash -g projops labalice-m creates /home/labalice from /etc/skel. -g projops sets the primary group; without it, useradd creates a private group matching the username.
A new account locks password login until you set one — shadow status markers such as !! mean no usable password yet. Check that state before you assign a password:
sudo passwd -S labalicelabalice L 2026-08-07 0 99999 7 -1The L status means the password is locked. Set a password interactively next:
sudo passwd labalicepasswd prompts twice on the terminal and does not print the hash. For scripts, echo 'labalice:password' | sudo chpasswd works on RHEL-family systems — use a strong password and clear shell history afterward.
Confirm the account now reports an active password:
sudo passwd -S labalicelabalice P 2026-08-07 0 99999 7 -1P means a password hash is set. Check the passwd entry and home directory ownership:
getent passwd labalicelabalice:x:1002:1002:Alice Ops:/home/labalice:/bin/bashField four (1002) matches the primary GID from projops. The home path and /bin/bash shell are what login programs read.
ls -ld /home/labalicedrwx------. 3 labalice projops 78 Aug 7 22:06 /home/labaliceThe directory owner is labalice and the group is projops because that is the primary GID.
For Ubuntu and Debian, adduser is an interactive wrapper; on RHEL, adduser is typically a symlink to useradd. Distro-specific create-user workflows are covered in create user in Linux.
Add a second user with a private primary group and projops as supplementary:
sudo useradd -m -c 'Bob Ops' -s /bin/bash -G projops labbob-G projops adds supplementary membership only; labbob still gets a default primary group named labbob. Verify both GIDs in one line:
id labbobuid=1003(labbob) gid=1003(labbob) groups=1003(labbob),1002(projops)labbob keeps labbob as primary group and joins projops as supplementary — a common layout for shared project directories.
Inspect Existing Users and Groups
With projops, labalice, and labbob in place, inspect what the databases store. Start with id when you know the username:
id labaliceuid=1002(labalice) gid=1002(projops) groups=1002(projops)The first gid is the primary group. Additional groups= entries are supplementary memberships.
getent follows NSS (/etc/nsswitch.conf), so it reflects the same databases applications use — not only flat files:
getent passwd labalicelabalice:x:1002:1002:Alice Ops:/home/labalice:/bin/bashFields are name:password_placeholder:UID:GID:comment:home:shell. The x in the password field means the hash lives in /etc/shadow.
List a group entry the same way:
getent group projopsprojops:x:1002:A trailing member list after the third colon is empty until users join as supplementary members.
UID_MIN separates the normal allocation range from most system accounts. It is useful for finding likely regular users, but UID alone does not prove an account can log in. Check the login shell as well. Special accounts such as nobody may live outside both ranges — on RHEL 8 and later, nobody uses UID 65534.
Filter on the regular-user UID range from /etc/login.defs — typically UID_MIN 1000 on RHEL. The upper bound excludes nobody at 65534:
getent passwd | awk -F: '$3>=1000 && $3<65534 {print $1, $3, $6, $7}'golinuxcloud 1000 /home/golinuxcloud /bin/bash
student 1001 /home/student /bin/bash
labalice 1002 /home/labalice /bin/bash
labbob 1003 /home/labbob /bin/bashThe shell column shows whether the account is configured for interactive login — /sbin/nologin or /bin/false blocks a normal shell session even when the UID is in range. For more listing patterns, see list users in Linux and list groups.
Modify an Existing User
usermod changes account attributes without recreating the account. Change labbob login shell to /bin/sh:
sudo usermod -s /bin/sh labbobRead field seven from passwd to confirm the shell path stored for login:
getent passwd labbob | awk -F: '{print $1 ":" $7}'labbob:/bin/shThe shell column now shows /bin/sh instead of /bin/bash.
Update the GECOS comment field shown by finger and some account tools:
sudo usermod -c 'Bob Ops (contractor)' labbobusermod updates /etc/passwd silently when the name is valid.
Move a home directory when the path changes. labbob was created with /home/labbob; point the account at a new location and move the existing contents with -m:
sudo mkdir -p /srv/homePoint the passwd home field at the new path and move existing files with -m:
sudo usermod -d /srv/home/labbob -m labbob-m creates the destination when needed and moves files from the old home. Confirm field six in passwd and the directory on disk:
getent passwd labboblabbob:x:1003:1003:Bob Ops (contractor):/srv/home/labbob:/bin/shls -ld /srv/home/labbobdrwx------. 3 labbob labbob 78 Aug 7 22:19 /srv/home/labbobThe home path in passwd keeps the updated comment and shell from the earlier usermod steps — usermod -d with -m moves the directory contents only.
Rename a login with usermod -l newname oldname only after you update cron jobs, mail aliases, and paths that still use the old name.
Lock password authentication:
sudo usermod -L labbob-L prefixes ! on the password hash in /etc/shadow, which disables password authentication but does not necessarily disable the entire account. SSH keys or other authentication mechanisms may still work. Unlock the password with usermod -U. To expire the local account itself, set an account expiration date — for example usermod --expiredate 1 labbob.
Delete a User
userdel removes the account from local databases. On production hosts, confirm nothing still depends on the account before you delete it.
Check for running processes owned by the user first — the ps command filters by UID, and userdel refuses deletion while processes still run:
ps -u labbob -o pid,cmdAn empty result means no processes on this host currently run as labbob. Stop or kill any listed PIDs before you continue.
Search for files outside the home directory that userdel -r will remove, but exclude that home tree:
sudo find /srv \
-path /srv/home/labbob -prune -o \
-user labbob -print 2>/dev/nullAn empty result means no additional files under /srv still reference that username. The prune skips /srv/home/labbob on purpose — userdel -r handles that tree. Reassign anything returned with chown or archive it before deletion.
Capture the UID if you plan to hunt orphans after the name disappears from /etc/passwd:
LABBOB_UID=$(id -u labbob)Delete labbob and remove the home directory:
sudo userdel -r labbob-r removes /srv/home/labbob and the mail spool when present.
Confirm the account is gone:
getent passwd labbobgetent exits with status 2 when the name is missing.
After userdel, orphaned files show numeric UID instead of a username. Search by the captured UID — find -user expects a name or UID that still resolves:
sudo find / -uid "$LABBOB_UID" 2>/dev/nullOn this lab host the command returns nothing when every file was already owned under the home tree userdel -r removed. On production it surfaces stray paths that still carry the old numeric owner.
Create and Delete Groups
Create a group with an explicit GID when matching another system:
sudo groupadd -g 1010 buildopsgroupadd writes the new line to /etc/group without terminal output when the GID is free.
Rename the group when a project name changes:
sudo groupmod -n buildops-renamed buildopsConfirm the new name maps to the same GID:
getent group buildops-renamedbuildops-renamed:x:1010:Keep buildops-renamed for the primary-group change in the next section. Delete it only after no user lists that GID as primary.
Manage Primary and Supplementary Membership
labalice was created with projops as primary group. Change the primary GID in field four of /etc/passwd to buildops-renamed:
sudo usermod -g buildops-renamed labaliceCheck that gid= in id output matches the new primary group:
id labaliceuid=1002(labalice) gid=1010(buildops-renamed) groups=1010(buildops-renamed)When usermod -g changes the primary group, shadow-utils adjusts qualifying files inside the user's home directory. Files outside the home need manual ownership correction with chown.
Restore projops as the primary group for the rest of this lab:
sudo usermod -g projops labaliceConfirm the primary group is back on projops:
id labaliceuid=1002(labalice) gid=1002(projops) groups=1002(projops)Delete the temporary group now that no account uses it as primary:
sudo groupdel buildops-renamedgroupdel succeeds once no /etc/passwd entry lists GID 1010 as primary.
Create a neutral supplementary group for membership demos — labextra works on every distro without relying on wheel or sudo. Reuse GID 1010 freed when buildops-renamed was deleted so the id output stays predictable:
sudo groupadd -g 1010 labextraAppend labalice without changing the primary group — note -a:
sudo usermod -aG labextra labaliceFrom the administrator shell, id labalice reads the account databases and shows labextra immediately:
id labaliceuid=1002(labalice) gid=1002(projops) groups=1002(projops),1010(labextra)Before you remove that membership, switch into labextra from labalice's session (su - labalice first if you are root). newgrp only works while the user is still a member:
newgrp labextraInside the subshell, id with no username reports this process's credentials — not the same view as id labalice run by root:
iduid=1002(labalice) gid=1010(labextra) groups=1010(labextra),1002(projops)Exit the subshell when finished:
exitReplace the entire supplementary list only when that is intentional — this drops labextra:
sudo usermod -G projops labaliceRun id again — labextra should no longer appear in groups=:
id labaliceuid=1002(labalice) gid=1002(projops) groups=1002(projops)labextra disappeared because -G without -a overwrites supplementary groups. That is the most common membership mistake on exams and production.
Remove one supplementary group by rewriting the list or use gpasswd -d user group on systems where it is available.
Shared directory example
Create a team directory, assign group ownership, and set the setgid bit so new files inherit projops:
sudo mkdir -p /srv/projops && sudo chown root:projops /srv/projops && sudo chmod 2770 /srv/projopsList the directory to read owner, group, and permission mode:
ls -ld /srv/projopsdrwxrws---. 2 root projops 6 Aug 7 22:06 /srv/projopsThe s in the group execute position is setgid: new files created here inherit projops as their group. Members need primary or supplementary membership in projops to use the directory.
Apply New Group Membership
Group changes land in the account databases immediately, but an already-running shell keeps the credentials it started with. As root, id labalice shows the updated membership from getent and /etc/group right away. Inside labalice's open shell, id with no username still shows that process's old group set until the session ends — the newgrp labextra demo in the previous section illustrated that difference.
For the complete updated group list, start a new login or SSH session. newgrp group is useful when you specifically want to switch the current GID to a group you already belong to without logging out. It does not reload every supplementary membership change, and it cannot grant a group you were removed from — run it while membership is still present, as with labextra above.
Configure Defaults for New Users
Distribution defaults control the next useradd invocation. On RHEL, start with UID/GID floors and home creation in /etc/login.defs:
grep -E '^(UID_MIN|GID_MIN|CREATE_HOME)' /etc/login.defsUID_MIN 1000
GID_MIN 1000
CREATE_HOME yesHuman accounts normally take UIDs from 1000 upward; most system accounts are allocated below that range.
Skeleton path and default shell come from /etc/default/useradd:
grep -E '^(HOME|SHELL|CREATE)' /etc/default/useraddHOME=/home
SHELL=/bin/bash
CREATE_MAIL_SPOOL=yesNew home directories copy starter files from /etc/skel:
ls -la /etc/skel/drwxr-xr-x. 3 root root 78 Aug 3 11:36 .
-rw-r--r--. 1 root root 18 Oct 29 2024 .bash_logout
-rw-r--r--. 1 root root 144 Oct 29 2024 .bash_profile
-rw-r--r--. 1 root root 522 Oct 29 2024 .bashrcThose dotfiles become the new user's initial shell environment. Password expiration and aging rules are covered in the chage guide — not duplicated here.
Find Files Owned by a User or Group
Before userdel, locate files by username:
sudo find /home/labalice -user labalice/home/labalice
/home/labalice/.bash_logout
/home/labalice/.bash_profilePaths under the home directory confirm which files still carry the username.
Search by group when cleaning shared trees:
sudo find /srv/projops -group projops/srv/projopsOnly the directory inode matches so far — no project files exist under /srv/projops yet.
After the user is deleted, the same search uses the numeric UID:
sudo find / -uid 1002 2>/dev/nullOn this lab host the command returns nothing once ownership was cleaned up; on production it surfaces orphaned files that still show 1002 instead of a username.
Reassign ownership with chown or archive data before you remove the account.
Troubleshoot User and Group Problems
| Symptom | Likely cause | Fix |
|---|---|---|
useradd: user 'name' already exists |
Login name taken | Pick another name or userdel the old account after backup |
useradd: UID N is not unique |
UID in use | getent passwd N; choose another UID with -u |
useradd: warning: home directory already exists |
Path not empty | Remove or pick -d; use -M to skip home creation |
| Home owned by root after create | Missing -m or wrong skel copy |
chown -R user:group /home/user; recreate with -m |
id missing new group |
Old shell session | Log in again; newgrp group switches current GID only |
usermod -G dropped a supplementary group |
Replaced list without -a |
usermod -aG labextra user |
| User cannot log in | Locked password (! in shadow), account expiration, nologin shell, or bad password |
usermod -U unlocks password only; clear expiration with usermod --expiredate '' user; set shell to /bin/bash; reset password |
userdel: user is currently used by process |
Active sessions or daemons | ps -u user; stop services; pkill -u user as last resort |
groupdel: cannot remove the primary group |
User still has that primary GID | usermod -g othergroup user then retry |
getent works but grep /etc/passwd does not |
NSS LDAP/SSSD source | Use getent for verification on integrated systems |
User and Group Quick Reference
| Task | Command |
|---|---|
| Show UID, GID, groups | id username |
| Query passwd database | getent passwd name |
| Query group database | getent group name |
| Create user with home | sudo useradd -m -s /bin/bash name |
| Set password | sudo passwd name |
| Modify user | sudo usermod [options] name |
| Delete user + home | sudo userdel -r name |
| Create group | sudo groupadd name |
| Rename group | sudo groupmod -n new old |
| Delete group | sudo groupdel name |
| Set primary group | sudo usermod -g group user |
| Append supplementary group | sudo usermod -aG group user |
| Replace supplementary groups | sudo usermod -G g1,g2 user |
| Pick up full group set | New login or SSH session |
| Switch current GID without logout | newgrp group |
| Files by user | sudo find /path -user name |
| Files by group | sudo find /path -group name |
References
- passwd(5) —
/etc/passwdformat - shadow(5) —
/etc/shadowformat - group(5) —
/etc/groupformat - useradd(8)
- usermod(8)
- userdel(8)
- groupadd(8)
Summary
Linux user and group management is a lifecycle: create a group with groupadd, add users with useradd -m, set passwords, adjust accounts with usermod, and remove them with userdel -r after you check processes and file ownership. id and getent show the effective identity applications see; primary group comes from /etc/passwd field four, while supplementary groups come from /etc/group.
The exam pitfall is usermod -G without -a, which replaces every supplementary group instead of appending one. Another common gap is expecting id inside an open shell to match what root sees after usermod — only a new login picks up the full updated group set; newgrp switches the current GID to one group. Defaults in /etc/login.defs, /etc/default/useradd, and /etc/skel shape the next account you create.
Before deleting a user, run find -user and plan ownership changes for files outside the home directory. Password aging and sudo policy are covered in the chage and sudo guides rather than here.

