User account management is one of the most fundamental administrative tasks on any Linux server. On Red Hat Enterprise Linux 9, every process, file, and network socket is owned by a user and group, making correct account hygiene essential for both security and auditability. Understanding the full lifecycle — creation, modification, and removal — and knowing how to apply principle of least privilege through supplementary groups prevents privilege escalation and keeps audit trails meaningful.

This tutorial covers the complete toolkit for user and group management on RHEL 9: useradd, usermod, userdel, groupadd, groupmod, groupdel, passwd, and the PAM password policy layer. You will create service accounts, standard users, and understand the difference between system accounts and login accounts.

All commands in this guide are run as root or with sudo.

Prerequisites

  • RHEL 9 server with root or sudo access
  • Basic familiarity with the Linux command line
  • Completed initial server setup (hostname, SSH, firewall)

Understanding the User Account Files

Before creating users, understand where account data lives:

  • /etc/passwd — one line per account: username:x:UID:GID:comment:home:shell
  • /etc/shadow — hashed passwords and aging policies, readable only by root
  • /etc/group — group definitions: groupname:x:GID:member1,member2
  • /etc/gshadow — group passwords and administrators

On RHEL 9, UIDs 0 through 999 are reserved for system accounts. Human login accounts start at UID 1000 (configured in /etc/login.defs as UID_MIN).

Step 1 — Create a Standard User Account

Create a new user with a home directory, default shell, and a comment field identifying their full name:

useradd -m -s /bin/bash -c "Jane Doe" janedoe

Flags explained:

  • -m — create a home directory at /home/janedoe populated from /etc/skel
  • -s /bin/bash — set the login shell to Bash
  • -c "Jane Doe" — GECOS comment field, shown in some log tools

Set the password immediately:

passwd janedoe

Verify the account was created:

id janedoe
getent passwd janedoe

Step 2 — Create a System Service Account (No Login)

Applications like Nginx, PostgreSQL, and custom daemons should run under dedicated service accounts, not as root. Service accounts have no valid shell to prevent interactive login:

useradd -r -s /sbin/nologin -c "Nginx Service Account" nginx

The -r flag creates a system account (UID below 1000). The /sbin/nologin shell immediately disconnects any login attempt, making interactive login impossible even with a valid password.

Step 3 — Specify a Custom UID, GID, and Home Directory

For environments where UIDs must be consistent across multiple servers (NFS, LDAP, Ansible-managed fleets), specify them explicitly:

groupadd -g 2001 appteam
useradd -u 2001 -g appteam -G wheel,docker -m -d /srv/appuser -s /bin/bash appuser

Here -G wheel,docker adds the user to the wheel (sudo) and docker supplementary groups. The -d /srv/appuser sets a non-standard home directory.

Step 4 — Modify an Existing User

Use usermod to change any attribute of an existing account:

# Add user to an additional group (append, do not replace existing groups)
usermod -aG docker janedoe

# Change the login shell
usermod -s /bin/zsh janedoe

# Lock the account (prepend ! to the password hash in /etc/shadow)
usermod -L janedoe

# Unlock the account
usermod -U janedoe

# Set account expiry date
usermod -e 2026-12-31 janedoe

Critical: Always use -aG (append and group) when adding supplementary groups. Omitting -a replaces all supplementary groups, potentially locking the user out of sudo.

Step 5 — Set Password Aging Policies

RHEL 9 enforces password aging through the chage command:

# Show current aging settings
chage -l janedoe

# Set maximum password age to 90 days, warn 14 days before expiry
chage -M 90 -W 14 janedoe

# Force password change on next login
chage -d 0 janedoe

Set system-wide defaults in /etc/login.defs:

PASS_MAX_DAYS   90
PASS_MIN_DAYS   7
PASS_WARN_AGE   14

Step 6 — Configure Password Complexity with pwquality

RHEL 9 uses pam_pwquality to enforce password strength. Edit /etc/security/pwquality.conf:

# Minimum password length
minlen = 14

# Require at least 1 uppercase character
ucredit = -1

# Require at least 1 digit
dcredit = -1

# Require at least 1 special character
ocredit = -1

# Reject passwords that contain the username
usercheck = 1

# Reject passwords from the cracklib dictionary
dictcheck = 1

Step 7 — Manage Groups

# Create a group
groupadd developers

# Add multiple users to a group
usermod -aG developers alice
usermod -aG developers bob

# Rename a group
groupmod -n devteam developers

# List all members of a group
getent group devteam

# Remove a user from a group
gpasswd -d alice devteam

Step 8 — Delete a User Account

Before deletion, decide whether to keep the home directory. Best practice is to lock first, archive, then delete after a retention period:

# Lock the account immediately
usermod -L janedoe

# Archive the home directory
tar -czf /backups/janedoe-home-$(date +%F).tar.gz /home/janedoe

# Delete user and home directory (permanent)
userdel -r janedoe

Step 9 — Find Files Owned by a Deleted User

After deletion, files owned by the old UID show as a numeric UID (orphaned files). Find and reassign them:

# Find orphaned files
find / -nouser -o -nogroup 2>/dev/null | grep -v /proc

# Reassign orphaned files to a new owner
find / -nouser 2>/dev/null -exec chown newowner {} ;

Step 10 — Bulk User Management with a Shell Script

For onboarding multiple users from a list, use a simple shell script:

#!/bin/bash
# users.txt format: username:fullname:group
while IFS=: read -r username fullname group; do
    useradd -m -s /bin/bash -c "$fullname" -G "$group" "$username"
    echo "$username:$(openssl rand -base64 12)" | chpasswd
    chage -d 0 "$username"   # Force password reset at first login
    echo "Created: $username"
done < users.txt

Verification

# Confirm user exists and check UID/groups
id janedoe

# Confirm home directory ownership and permissions
ls -la /home/janedoe

# Confirm group membership
groups janedoe

# Audit all accounts with UID 0 (should only be root)
awk -F: '$3==0' /etc/passwd

# List all accounts with a valid login shell
grep -v "/sbin/nologin|/bin/false|/sbin/halt|/sbin/shutdown" /etc/passwd | cut -d: -f1

Troubleshooting

  • useradd: user already exists — check with id username; modify with usermod instead of recreating.
  • sudo: user is not in the sudoers file — run usermod -aG wheel username then have the user log out and back in.
  • PAM rejects the new password — RHEL 9 enforces complexity via pam_pwquality. Temporarily reduce minlen in /etc/security/pwquality.conf if setting a temporary password for a service account.

Security Considerations

  • Audit all accounts with UID 0 — there should only ever be one: root.
  • Disable accounts immediately when staff leave. usermod -L username is instant.
  • Use service accounts for all daemons. Never run applications as root.
  • Review /etc/sudoers.d/ and /etc/sudoers regularly. Avoid NOPASSWD for human accounts.
  • Enable auditing of user management commands: add -w /etc/passwd -p wa -k user-modify to /etc/audit/rules.d/audit.rules.

Conclusion

You now have a complete understanding of user and group lifecycle management on RHEL 9. You can create standard login accounts, system service accounts with no login shell, manage group memberships, enforce password aging and complexity, and clean up accounts securely. These skills are prerequisites for every other tutorial in this RHEL 9 series.

Next steps: How to Configure sudo and Sudoers on RHEL 9, How to Configure the Firewall on RHEL 9, and How to Perform a System Security Audit with auditd on RHEL 9.