Linux User and Group Administration: Complete Guide

User and group management is central to Linux security. Every process runs as a user, every file is owned by a user and a group, and all access decisions flow from these identities. For RHCA, you must understand the complete user management ecosystem — from the internal file formats to advanced sudo policies and password aging.

The Linux User Identity Model

Linux identifies users by a numeric UID (User ID), not by name. The username is a human-readable label stored in /etc/passwd and mapped to the UID at login. Internally, the kernel works exclusively with UIDs and GIDs. This means:

  • Two accounts with different usernames but the same UID are treated as the same user by the kernel
  • Renaming a user in /etc/passwd does not change file ownership — the UID stays the same
  • If you delete a user with UID 500 and create a new user who gets UID 500, they will own all files the previous user owned

User Types and UID Ranges

TypeUID Range (RHEL 7+)ShellHome DirPurpose
Root0/bin/bash/rootFull administrative access
Static system users1–200/sbin/nologin/var/svcnameCreated by OS install for core services (sync, shutdown, halt)
Dynamic system users201–999/sbin/nologin/var/svcnameCreated when packages are installed (apache, mysql, postfix)
Normal users1000–60000/bin/bash/home/usernameHuman users
Sudo userssame as normal/bin/bash/home/usernameNormal users with elevated privileges

Why do system users have /sbin/nologin as shell? Setting the shell to /sbin/nologin prevents interactive login for that account. Even if someone knows the password (or exploits a vulnerability), they cannot get a shell. This is a security best practice — services should run as non-interactive users.

The Authentication File System

/etc/passwd — User Account Database

This file has been part of Unix since the 1970s. Originally it stored password hashes in the second field — but since passwords are now in /etc/shadow, the second field always contains x.

# Format (7 colon-separated fields):
username:x:UID:GID:Comment/GECOS:HomeDir:LoginShell

# Real example:
root:x:0:0:root:/root:/bin/bash
apache:x:48:48:Apache:/usr/share/httpd:/sbin/nologin
raju:x:1001:1001:Raju Kumar:/home/raju:/bin/bash

# Fields explained:
# username  = login name (max 32 chars, no spaces)
# x         = password placeholder (actual hash is in /etc/shadow)
# UID       = numeric user ID
# GID       = primary group ID
# Comment   = GECOS field — full name, room, phone (used by finger, mail)
# HomeDir   = absolute path to home directory
# LoginShell = program executed at login (/bin/bash for users, /sbin/nologin for services)

/etc/shadow — Secure Password Database

When shadow passwords are enabled (the default since the 1990s), hashed passwords are moved from the world-readable /etc/passwd to the root-only /etc/shadow. This prevents offline password cracking attacks by non-privileged users.

# Format (9 colon-separated fields):
username:hashed_password:last_change:min_days:max_days:warn_days:inactive:expire:reserved

# Real example:
raju:$6$rounds=5000$Ab1c2...$xyz....:19200:0:90:7:3:19600:

# Fields explained:
# username      = same as /etc/passwd
# hashed_pwd    = $algorithm$salt$hash
#                 $1 = MD5 (old, weak)
#                 $5 = SHA-256
#                 $6 = SHA-512 (current standard)
#                 !! = locked account
#                 *  = account cannot login
# last_change   = days since Jan 1 1970 password was last changed
# min_days      = minimum days before password can be changed (0 = anytime)
# max_days      = maximum days password is valid (99999 = never expires)
# warn_days     = days before expiry to warn user
# inactive      = days after expiry before account is locked
# expire        = absolute expiry date (days since Jan 1 1970)

/etc/group — Group Database

# Format:
groupname:x:GID:member1,member2,...

# Example:
wheel:x:10:raju,sam
oracle:x:1002:raju,gopal,ram

/etc/gshadow — Group Shadow File

Stores group passwords (rarely used) and group administrators.

# Format:
groupname:password:admin_list:member_list

User Creation In Depth

# Full useradd syntax:
# useradd [OPTIONS] username

# Options:
# -u UID        specify UID (must be unique)
# -g GID        specify primary group (GID or group name)
# -G grp1,grp2  specify secondary groups
# -c "comment"  GECOS field (full name, etc.)
# -d /home/dir  home directory path
# -s /bin/bash  login shell
# -m            create home directory (default on most systems)
# -M            do NOT create home directory
# -r            create a system account (UID < 1000, no home dir by default)
# -e YYYY-MM-DD account expiry date
# -f days       password inactive days after expiry
# -o            allow non-unique UID (create duplicate UID)

# Full example:
# useradd -u 1500 -g 1500 -G wheel,oracle -c "Database Admin" \
    -d /home/dba -s /bin/bash dba

# Set password immediately:
# passwd dba

# Check what was created:
# id dba
# grep dba /etc/passwd /etc/shadow /etc/group

What Happens When useradd Runs

  1. A new line is added to /etc/passwd
  2. A new line is added to /etc/shadow with locked password (!!)
  3. A new group is created in /etc/group (same name as user, same GID as UID)
  4. Home directory is created at the specified path
  5. Files from /etc/skel/ are copied to the home directory (.bash_profile, .bashrc, .bash_logout)
  6. A mailbox is created at /var/spool/mail/username

The /etc/skel Directory

Every file in /etc/skel/ is copied to new users' home directories. This is where you place default configuration files that should appear for all new users.

# Standard /etc/skel contents:
# .bash_logout  — executed when user logs out
# .bash_profile — executed at login (sets PATH, environment variables)
# .bashrc       — executed for each new shell (aliases, functions)

# Add a custom file for all new users:
# echo "export EDITOR=vim" >> /etc/skel/.bashrc

# Apply to existing user manually:
# cp /etc/skel/.bashrc /home/raju/
# chown raju:raju /home/raju/.bashrc

The Bash Environment Files

Understanding which file is sourced when is critical for diagnosing login/shell issues:

FileWhen ExecutedTypical Use
/etc/profileLogin shell — ALL usersSystem-wide environment, PATH
/etc/bashrcNon-login interactive shell — ALL usersSystem-wide aliases, PS1
~/.bash_profileLogin shell — specific userUser-specific environment, calls .bashrc
~/.bashrcNon-login interactive shellUser aliases, functions, custom prompt
~/.bash_logoutOn logoutCleanup tasks, clear history

Password Aging and Security Policies

Password aging prevents users from using the same password indefinitely and implements grace periods for expired accounts.

# View current password aging for a user:
# chage -l raju

# Set comprehensive password policy:
# chage -m 7 raju    # minimum 7 days before they can change password again
# chage -M 90 raju   # maximum 90 days validity
# chage -W 14 raju   # warn 14 days before expiry
# chage -I 7 raju    # 7-day inactive grace period after expiry
# chage -E 2026-12-31 raju  # account expires Dec 31 2026

# Force password change on next login:
# chage -d 0 raju    # sets last_change to 0 (epoch) = expired immediately

# System-wide defaults in /etc/login.defs:
PASS_MAX_DAYS   90      # maximum password age
PASS_MIN_DAYS   7       # minimum password age
PASS_WARN_AGE   14      # warning days before expiry
PASS_MIN_LEN    8       # minimum password length

# Apply changes: edit the file, restart nothing (read at useradd time)

Account Locking and Unlocking

# Lock account (adds ! prefix to password hash in /etc/shadow):
# usermod -L raju
# passwd -l raju

# Unlock account:
# usermod -U raju
# passwd -u raju

# Check lock status:
# passwd -S raju
# grep raju /etc/shadow             # look for ! prefix in password field

# Expire account immediately:
# usermod -e 1970-01-01 raju       # set expiry to past date

# Disable login without locking (change shell to nologin):
# usermod -s /sbin/nologin raju

# Restore login shell:
# usermod -s /bin/bash raju

Groups In Depth

Primary vs Secondary Groups

Every user has exactly one primary group (stored in /etc/passwd 4th field). New files are owned by the primary group by default. A user can belong to up to 15 secondary groups, which grant additional access to group-owned resources.

# Check all groups for a user:
# id raju                           # shows uid, gid, and all groups
# groups raju                       # simplified list

# Create group:
# groupadd -g 2000 oracle

# Add user to secondary group (preserves existing groups):
# usermod -aG oracle raju           # -a = append, -G = secondary groups
# WARNING: usermod -G oracle raju (without -a) REPLACES all secondary groups

# Remove user from group:
# gpasswd -d raju oracle

# Change user's primary group:
# usermod -g oracle raju

# Create group with admin user:
# groupadd developers
# gpasswd -A raju developers         # raju is now group admin
# gpasswd -a sam developers          # group admin can add members

sudo — Delegated Root Privilege

sudo (superuser do) allows specific users to run specific commands as root (or another user) without knowing the root password. All sudo commands are logged to /var/log/secure.

Why sudo Instead of su?

  • Accountability: Every sudo command is logged with the user who ran it
  • Principle of least privilege: Users only get access to commands they need
  • No root password sharing: Each admin uses their own password
  • Time-limited tokens: sudo prompts for password after timeout (default 5 minutes)

The sudoers File Structure

# ALWAYS edit with visudo (validates syntax before saving):
# visudo

# Basic format:
# WHO  WHERE=(AS_WHOM)  WHAT
# user host=(runas)     command

# Give user all privileges:
raju ALL=(ALL) ALL

# Give user all privileges without password:
raju ALL=(ALL) NOPASSWD: ALL

# Allow only specific commands:
raju ALL=/usr/sbin/useradd,/usr/sbin/passwd

# Allow specific commands without password:
raju ALL=NOPASSWD:/usr/sbin/useradd,/usr/bin/systemctl restart httpd

# Allow a group:
%oracle ALL=(ALL) ALL
%wheel  ALL=(ALL) ALL              # wheel group = traditional admin group

# Command aliases (group commands):
Cmnd_Alias NETWORKING = /sbin/ifconfig,/sbin/route,/usr/sbin/iptables
Cmnd_Alias SERVICES   = /usr/sbin/service,/usr/bin/systemctl

raju ALL=NETWORKING,SERVICES

# User aliases:
User_Alias DBAS = raju, gopal, ram
DBAS ALL=/usr/bin/mysql

# Host aliases (restrict sudo to specific servers):
Host_Alias WEBSERVERS = web1,web2,web3
raju WEBSERVERS=(ALL) ALL

# Force re-authentication for every sudo command:
Defaults timestamp_timeout=0

# Only for specific users:
Defaults:raju,gopal timestamp_timeout=0

File Permissions — Complete Reference

# Permission structure:
# -rwxrwxrwx  (type + owner + group + others)
#
# Types: - regular file, d directory, l symlink,
#        c char device, b block device, s socket, p pipe
#
# Permissions: r=4 (read), w=2 (write), x=1 (execute), -=0 (none)

# For FILES:     r=cat/read, w=modify/delete, x=execute
# For DIRECTORIES: r=ls (list), w=create/delete files inside, x=cd (enter)

# Numeric (octal) examples:
# chmod 755 file  = rwxr-xr-x (owner full, group/others read+execute)
# chmod 644 file  = rw-r--r-- (owner read+write, others read only)
# chmod 700 dir   = rwx------ (only owner can access)
# chmod 777 dir   = rwxrwxrwx (everyone full access — use carefully)

# Symbolic examples:
# chmod u+x file          add execute for owner
# chmod g-w file          remove write for group
# chmod o=r file          set others to read-only exactly
# chmod a+r file          add read for all (user, group, others)
# chmod u+x,g-w,o-r file  compound changes

# Recursive (apply to directory and all contents):
# chmod -R 755 /var/www/html

# Change ownership:
# chown raju file                    change owner
# chown raju:oracle file             change owner and group
# chown :oracle file                 change only group
# chgrp oracle file                  change only group
# chown -R raju:oracle /data/        recursive

umask — Default Permission Control

umask subtracts permissions from the maximum default. It is a safety filter that ensures new files are not created world-writable by default.

# Default maximum:
# Files: 666 (rw-rw-rw-)  — execute never set by default for files
# Dirs:  777 (rwxrwxrwx)

# Default umask: 0022 for root, 0002 for normal users
# Result for root:
#   files: 666 - 022 = 644 (rw-r--r--)
#   dirs:  777 - 022 = 755 (rwxr-xr-x)

# Check current umask:
# umask                              # shows 0022

# Set temporarily:
# umask 027                          # owner full, group read/execute, others none

# Set permanently for a user:
# echo "umask 027" >> ~/.bashrc
# source ~/.bashrc

# Set system-wide:
# echo "umask 022" >> /etc/bashrc

Special Permission Bits

SUID (Set User ID)

When SUID is set on an executable, it runs as its owner (not as the user who runs it). Classic example: /usr/bin/passwd — a normal user needs to write to /etc/shadow (root-owned), so passwd has SUID set to root.

# Find all SUID files (security audit):
# find / -perm /4000 -type f 2>/dev/null

# Set SUID:
# chmod u+s /usr/sbin/some_command
# chmod 4755 /usr/sbin/some_command

# Visible in ls:
# ls -l /usr/bin/passwd
# -rwsr-xr-x  (s in owner execute position = SUID)

SGID (Set Group ID)

On directories: files created inside inherit the directory's group (not the creator's primary group). Used for shared project directories where all team members need to share ownership.

# Set SGID on shared directory:
# chmod g+s /shared/project
# chmod 2775 /shared/project

# Verify:
# ls -ld /shared/project
# drwxrwsr-x  (s in group execute position = SGID)

Sticky Bit

On directories: users can only delete or rename files they own, even if they have write permission on the directory. This is why /tmp is world-writable but users cannot delete each other's temporary files.

# Set sticky bit:
# chmod o+t /tmp
# chmod 1777 /tmp

# Verify:
# ls -ld /tmp
# drwxrwxrwt  (t in others execute position = sticky)

ACL — Access Control Lists

Standard Unix permissions (owner/group/others) are sometimes too coarse. ACLs allow you to grant specific permissions to specific users or groups without changing ownership.

# Check ACL support:
# tune2fs -l /dev/sda1 | grep "Default mount options"  # ext4
# xfs_info /data | grep "acl"                           # xfs

# Set ACL for specific user:
# setfacl -m u:raju:rwx /shared/file

# Set ACL for specific group:
# setfacl -m g:oracle:rw /shared/file

# Set default ACL (applied to new files in directory):
# setfacl -dm u:raju:rwx /shared/

# View ACLs:
# getfacl /shared/file
# ls -l file                         # + sign at end means ACL is set

# Remove ACL:
# setfacl -x u:raju /shared/file
# setfacl -b /shared/file            # remove all ACLs

Recovering the Root Password

RHEL 6 Method

  1. Reboot, select boot entry, press e
  2. Select the kernel line, press e
  3. Add a space and 1 (single user mode) at end, press Enter
  4. Press b to boot
  5. At root prompt: passwd root

RHEL 7/8 Method

  1. Reboot, select boot entry, press e
  2. Find the linux16 line, press End
  3. Add: rd.break console=tty1 selinux=0
  4. Press Ctrl+X to boot
  5. At switch_root:/#: mount -o remount,rw /sysroot
  6. chroot /sysroot
  7. passwd root, set new password
  8. exit, exit (or reboot)