Manage Linux Users and Groups

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:

bash
sudo groupadd projops

groupadd exits silently when the name is new. Confirm the group exists and note its GID before you attach users:

bash
getent group projops
output
projops: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:

bash
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:

bash
sudo passwd -S labalice
output
labalice L 2026-08-07 0 99999 7 -1

The L status means the password is locked. Set a password interactively next:

bash
sudo passwd labalice

passwd 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:

bash
sudo passwd -S labalice
output
labalice P 2026-08-07 0 99999 7 -1

P means a password hash is set. Check the passwd entry and home directory ownership:

bash
getent passwd labalice
output
labalice:x:1002:1002:Alice Ops:/home/labalice:/bin/bash

Field four (1002) matches the primary GID from projops. The home path and /bin/bash shell are what login programs read.

bash
ls -ld /home/labalice
output
drwx------. 3 labalice projops 78 Aug  7 22:06 /home/labalice

The 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:

bash
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:

bash
id labbob
output
uid=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:

bash
id labalice
output
uid=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:

bash
getent passwd labalice
output
labalice:x:1002:1002:Alice Ops:/home/labalice:/bin/bash

Fields 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:

bash
getent group projops
output
projops: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:

bash
getent passwd | awk -F: '$3>=1000 && $3<65534 {print $1, $3, $6, $7}'
output
golinuxcloud 1000 /home/golinuxcloud /bin/bash
student 1001 /home/student /bin/bash
labalice 1002 /home/labalice /bin/bash
labbob 1003 /home/labbob /bin/bash

The 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:

bash
sudo usermod -s /bin/sh labbob

Read field seven from passwd to confirm the shell path stored for login:

bash
getent passwd labbob | awk -F: '{print $1 ":" $7}'
output
labbob:/bin/sh

The shell column now shows /bin/sh instead of /bin/bash.

Update the GECOS comment field shown by finger and some account tools:

bash
sudo usermod -c 'Bob Ops (contractor)' labbob

usermod 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:

bash
sudo mkdir -p /srv/home

Point the passwd home field at the new path and move existing files with -m:

bash
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:

bash
getent passwd labbob
output
labbob:x:1003:1003:Bob Ops (contractor):/srv/home/labbob:/bin/sh
bash
ls -ld /srv/home/labbob
output
drwx------. 3 labbob labbob 78 Aug  7 22:19 /srv/home/labbob

The 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:

bash
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:

bash
ps -u labbob -o pid,cmd

An 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:

bash
sudo find /srv \
  -path /srv/home/labbob -prune -o \
  -user labbob -print 2>/dev/null

An 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:

bash
LABBOB_UID=$(id -u labbob)

Delete labbob and remove the home directory:

bash
sudo userdel -r labbob

-r removes /srv/home/labbob and the mail spool when present.

Confirm the account is gone:

bash
getent passwd labbob

getent 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:

bash
sudo find / -uid "$LABBOB_UID" 2>/dev/null

On 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:

bash
sudo groupadd -g 1010 buildops

groupadd writes the new line to /etc/group without terminal output when the GID is free.

Rename the group when a project name changes:

bash
sudo groupmod -n buildops-renamed buildops

Confirm the new name maps to the same GID:

bash
getent group buildops-renamed
output
buildops-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:

bash
sudo usermod -g buildops-renamed labalice

Check that gid= in id output matches the new primary group:

bash
id labalice
output
uid=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:

bash
sudo usermod -g projops labalice

Confirm the primary group is back on projops:

bash
id labalice
output
uid=1002(labalice) gid=1002(projops) groups=1002(projops)

Delete the temporary group now that no account uses it as primary:

bash
sudo groupdel buildops-renamed

groupdel 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:

bash
sudo groupadd -g 1010 labextra

Append labalice without changing the primary group — note -a:

bash
sudo usermod -aG labextra labalice

From the administrator shell, id labalice reads the account databases and shows labextra immediately:

bash
id labalice
output
uid=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:

bash
newgrp labextra

Inside the subshell, id with no username reports this process's credentials — not the same view as id labalice run by root:

bash
id
output
uid=1002(labalice) gid=1010(labextra) groups=1010(labextra),1002(projops)

Exit the subshell when finished:

bash
exit

Replace the entire supplementary list only when that is intentional — this drops labextra:

bash
sudo usermod -G projops labalice

Run id again — labextra should no longer appear in groups=:

bash
id labalice
output
uid=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:

bash
sudo mkdir -p /srv/projops && sudo chown root:projops /srv/projops && sudo chmod 2770 /srv/projops

List the directory to read owner, group, and permission mode:

bash
ls -ld /srv/projops
output
drwxrws---. 2 root projops 6 Aug  7 22:06 /srv/projops

The 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:

bash
grep -E '^(UID_MIN|GID_MIN|CREATE_HOME)' /etc/login.defs
output
UID_MIN                  1000
GID_MIN                  1000
CREATE_HOME	yes

Human 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:

bash
grep -E '^(HOME|SHELL|CREATE)' /etc/default/useradd
output
HOME=/home
SHELL=/bin/bash
CREATE_MAIL_SPOOL=yes

New home directories copy starter files from /etc/skel:

bash
ls -la /etc/skel/
output
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 .bashrc

Those 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:

bash
sudo find /home/labalice -user labalice
output
/home/labalice
/home/labalice/.bash_logout
/home/labalice/.bash_profile

Paths under the home directory confirm which files still carry the username.

Search by group when cleaning shared trees:

bash
sudo find /srv/projops -group projops
output
/srv/projops

Only 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:

bash
sudo find / -uid 1002 2>/dev/null

On 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


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.


Frequently Asked Questions

1. What is the difference between a primary and supplementary group in Linux?

The primary group is the GID in the fourth field of /etc/passwd and the default group for new files the user creates. Supplementary groups are extra memberships listed in /etc/group and shown by id after the primary gid. useradd -g sets primary group; usermod -aG appends supplementary groups.

2. Why did my usermod -G change remove other groups?

usermod -G replaces the entire supplementary group list. Without -a, other supplementary groups disappear from id output. Use usermod -aG newgroup to append, or usermod -G group1,group2 when you intend to set the full list.

3. How do I delete a Linux user and their home directory?

userdel username removes the account from passwd, shadow, and group databases. Add -r to remove the home directory and mail spool when you are sure no needed data remains. Check find -user and running processes before you delete production accounts.

4. When does a new group membership take effect?

Group changes apply to new login sessions. An existing shell keeps its old credentials until you log in again. newgrp group switches the current GID to that group but does not reload every supplementary membership. Verify with id in the session you care about.

5. Should I edit /etc/passwd by hand?

Use useradd, usermod, userdel, groupadd, and groupdel instead. Hand-editing risks syntax errors and lockouts. getent passwd and getent group are safe read paths when NSS may include files plus remote sources.
Deepak Prasad

R&D Engineer

Founder of GoLinuxCloud with more than 15 years of expertise in Linux, Python, Go, Laravel, DevOps, Kubernetes, Git, Shell scripting, OpenShift, AWS, Networking, and Security. With extensive experience, he excels across development, DevOps, networking, and security, delivering robust and efficient solutions for diverse projects.

  • Go (programming language)
  • Python (programming language)
  • DevOps
  • Computer Security
  • Cloud Computing
  • Kubernetes
  • Linux
  • Ansible (software)