User and Group Management¶
Linux is a multi-user operating system. Every process runs as some user, every file is owned by some user, and access control decisions are made based on user and group identity. Whether you're creating accounts for team members, setting up service accounts for daemons, or configuring sudo access, user and group management is a daily sysadmin task.
The Identity Model: UIDs and GIDs¶
Every user on a Linux system has a numeric User ID (UID) and belongs to at least one Group ID (GID). The kernel doesn't care about usernames - it works entirely with these numbers. Usernames are just a human-friendly mapping stored in configuration files.
| UID Range | Purpose |
|---|---|
| 0 | Root (superuser) |
| 1-999 | System/service accounts (created by packages) |
| 1000+ | Regular user accounts |
The root user (UID 0) bypasses all permission checks. System accounts in the 1-999 range are used by services like sshd, www-data, and postgres - they typically have no login shell and no home directory.
Understanding the Identity Files¶
Three files form the core of Linux's local user database.
/etc/passwd¶
Every user account has a line in /etc/passwd:
| Field | Value | Meaning |
|---|---|---|
| 1 | jdoe |
Username |
| 2 | x |
Password placeholder (actual hash is in /etc/shadow) |
| 3 | 1001 |
UID |
| 4 | 1001 |
Primary GID |
| 5 | Jane Doe |
GECOS field (full name, contact info) |
| 6 | /home/jdoe |
Home directory |
| 7 | /bin/bash |
Login shell |
This file is world-readable - every process on the system needs to map UIDs to usernames. That's why password hashes were moved out of it decades ago.
/etc/shadow¶
/etc/shadow stores the actual password hashes and aging information. It's readable only by root:
| Field | Meaning |
|---|---|
| 1 | Username |
| 2 | Password hash (or !/* if locked) |
| 3 | Date of last password change (days since epoch) |
| 4 | Minimum days between password changes |
| 5 | Maximum days before password must be changed |
| 6 | Warning days before expiration |
| 7 | Days after expiration before account is disabled |
| 8 | Account expiration date |
A password field starting with ! or * means the account is locked - no password login is possible. This is standard for system accounts.
Password hash prefixes
The hash format indicates the algorithm: $1$ is MD5 (obsolete), $5$ is SHA-256, $6$ is SHA-512 (current default on most distributions), and $y$ is yescrypt (newer, used by Fedora 39+).
/etc/group¶
Group definitions live in /etc/group:
| Field | Meaning |
|---|---|
| 1 | Group name |
| 2 | Group password placeholder (rarely used) |
| 3 | GID |
| 4 | Comma-separated list of supplementary members |
A user's primary group is set in /etc/passwd (field 4). Supplementary groups are listed in /etc/group (field 4). A user can belong to many supplementary groups simultaneously.
Creating and Managing Users¶
useradd¶
useradd creates new user accounts:
# Create a user with default settings
sudo useradd jdoe
# Create a user with home directory, shell, and comment
sudo useradd -m -s /bin/bash -c "Jane Doe" jdoe
# Create a system account (no home dir, no login shell)
sudo useradd -r -s /sbin/nologin myservice
# Create a user with a specific UID and primary group
sudo useradd -u 1500 -g developers jdoe
# Create a user with supplementary groups
sudo useradd -m -s /bin/bash -G sudo,docker jdoe
| Flag | Purpose |
|---|---|
-m |
Create the home directory (copies files from /etc/skel) |
-s |
Set the login shell |
-c |
Set the GECOS comment (usually full name) |
-r |
Create a system account (UID below 1000) |
-u |
Specify the UID |
-g |
Set the primary group (name or GID) |
-G |
Set supplementary groups (comma-separated) |
-d |
Set the home directory path |
-e |
Set account expiration date (YYYY-MM-DD) |
The -m flag is important - without it on many distributions, the home directory isn't created. The home directory is populated from /etc/skel/, which typically contains default .bashrc, .profile, and .bash_logout files.
useradd vs adduser
On Debian/Ubuntu, adduser is a higher-level wrapper that interactively prompts for a password and details. On RHEL, adduser is just a symlink to useradd. For scripts and automation, always use useradd for consistent behavior across distributions.
Setting Passwords¶
useradd doesn't set a password. Use passwd afterward:
# Set or change a user's password (interactive)
sudo passwd jdoe
# Lock an account (prefix the hash with !)
sudo passwd -l jdoe
# Unlock an account
sudo passwd -u jdoe
# Force password change on next login
sudo passwd -e jdoe
# Check password status
sudo passwd -S jdoe
usermod¶
usermod modifies existing accounts:
# Change the login shell
sudo usermod -s /bin/zsh jdoe
# Change the home directory (and move files)
sudo usermod -d /home/newdir -m jdoe
# Add to supplementary groups (KEEP existing groups with -a)
sudo usermod -aG docker jdoe
# Change the username
sudo usermod -l newname oldname
# Lock the account
sudo usermod -L jdoe
# Set an account expiration date
sudo usermod -e 2026-12-31 jdoe
The -aG trap
sudo usermod -G docker jdoe without -a REPLACES all supplementary groups with just docker. Always use -aG (append) to add a user to a group without removing them from existing groups. This mistake has locked people out of sudo access.
userdel¶
# Remove a user (keeps home directory)
sudo userdel jdoe
# Remove a user and their home directory
sudo userdel -r jdoe
Creating and Managing Groups¶
Group Commands¶
# Create a new group
sudo groupadd developers
# Create a group with a specific GID
sudo groupadd -g 2000 developers
# Rename a group
sudo groupmod -n dev-team developers
# Delete a group (fails if it's any user's primary group)
sudo groupdel dev-team
# Add a user to a group (alternative to usermod -aG)
sudo gpasswd -a jdoe developers
# Remove a user from a group
sudo gpasswd -d jdoe developers
# List members of a group
getent group developers
Primary vs Supplementary Groups¶
Every user has exactly one primary group (set in /etc/passwd). When that user creates a file, it's owned by their primary group by default. Supplementary groups provide additional access without changing file creation defaults.
# Check a user's groups
groups jdoe
# Temporarily switch primary group for the current session
newgrp developers
After running newgrp developers, new files created in that session will be owned by the developers group instead of the user's default primary group.
The sudo System¶
sudo allows authorized users to run commands as root (or another user) without sharing the root password. Configuration lives in /etc/sudoers.
Viewing Your Privileges¶
# List what sudo commands you can run
sudo -l
# Run a command as another user
sudo -u postgres psql
# Open a root shell
sudo -i
# Run a command with another user's environment
sudo -u deploy -i -- /opt/app/deploy.sh
Editing sudoers Safely¶
Never edit /etc/sudoers directly - a syntax error can lock you out of sudo entirely. Always use visudo:
# Edit the main sudoers file
sudo visudo
# Edit a drop-in file (preferred for custom rules)
sudo visudo -f /etc/sudoers.d/developers
visudo validates the syntax before saving. If there's an error, it gives you the option to re-edit instead of saving a broken file.
sudoers Syntax¶
# User jdoe can run any command as any user on any host
jdoe ALL=(ALL:ALL) ALL
# Members of the sudo group can run any command
%sudo ALL=(ALL:ALL) ALL
# Members of developers can restart nginx without a password
%developers ALL=(ALL) NOPASSWD: /usr/bin/systemctl restart nginx
# User deploy can run specific deployment commands as www-data
deploy ALL=(www-data) NOPASSWD: /opt/app/deploy.sh, /usr/bin/systemctl restart myapp
The format is: who host=(runas_user:runas_group) commands
| Field | Meaning |
|---|---|
who |
Username or %groupname |
host |
Hostname this rule applies on (usually ALL) |
(runas) |
Which user/group to run as (ALL = any) |
commands |
Comma-separated list of allowed commands (full paths) |
Drop-in Files¶
Rather than modifying the main /etc/sudoers file, use drop-in files in /etc/sudoers.d/:
# /etc/sudoers.d/developers
%developers ALL=(ALL) NOPASSWD: /usr/bin/systemctl restart nginx, \
/usr/bin/systemctl restart myapp, \
/usr/bin/systemctl status *
NOPASSWD and security
NOPASSWD is convenient for automation and specific service management commands, but giving broad NOPASSWD: ALL access is effectively the same as giving away the root password. Limit NOPASSWD to specific commands that need to run non-interactively.
Drop-in files must have no . or ~ in the filename (sudoers ignores files with those characters) and must have mode 0440.
Switching Users¶
su (Switch User)¶
# Switch to root (starts a shell as root, keeps current environment)
su
# Switch to root with a login shell (loads root's environment)
su -
# Switch to another user with a login shell
su - jdoe
# Run a single command as another user
su - jdoe -c "whoami"
The difference between su and su - matters. Plain su keeps your current environment variables (PATH, HOME, etc.), which can cause confusing behavior when commands resolve differently. su - simulates a full login, loading the target user's shell profile and environment.
sudo -i vs sudo su¶
Both give you a root shell, but they work differently:
| Command | Mechanism | Logging |
|---|---|---|
sudo -i |
sudo opens a login shell directly | Logged in auth.log/secure with your username |
sudo su - |
sudo runs su, which opens a shell | Logged as "sudo su" - less specific |
su - |
su authenticates with root's password | Requires the root password |
sudo -i is preferred on modern systems because it uses your own password (verified against sudoers), logs your identity, and doesn't require sharing the root password.
PAM Basics¶
The Pluggable Authentication Modules (PAM) framework controls how authentication works on Linux. Every program that needs to verify a user's identity - login, sshd, sudo, su - goes through PAM.
How PAM Works¶
PAM configuration files in /etc/pam.d/ define a stack of modules that run in sequence for each authentication event. Each file corresponds to a service (e.g., /etc/pam.d/sshd, /etc/pam.d/sudo).
A typical PAM configuration line:
| Field | Meaning |
|---|---|
auth |
Module type (what this rule checks) |
required |
Control flag (what happens if this module fails) |
pam_unix.so |
The module to run |
nullok |
Module-specific options |
Module Types¶
| Type | Purpose |
|---|---|
auth |
Verify the user's identity (password check) |
account |
Check account restrictions (expiration, time-of-day access) |
password |
Handle password changes |
session |
Set up/tear down the user session (mount home dir, set limits) |
Control Flags¶
| Flag | Meaning |
|---|---|
required |
Must succeed, but continue checking other modules |
requisite |
Must succeed - fail immediately if it doesn't |
sufficient |
If this succeeds, skip remaining modules of this type |
optional |
Result is ignored unless it's the only module |
Common PAM Modules¶
| Module | Purpose |
|---|---|
pam_unix.so |
Standard password authentication against /etc/shadow |
pam_wheel.so |
Restrict su to members of the wheel group |
pam_limits.so |
Apply resource limits from /etc/security/limits.conf |
pam_faillock.so |
Lock accounts after repeated failed login attempts |
pam_pwquality.so |
Enforce password complexity requirements |
pam_google_authenticator.so |
Two-factor authentication via TOTP |
Practical Example: Restricting su to wheel Group¶
Edit /etc/pam.d/su and uncomment or add:
Now only members of the wheel group can use su to switch to root. This is a standard hardening measure.
Practical Example: Account Lockout¶
Configure /etc/security/faillock.conf:
This locks an account for 10 minutes after 5 failed login attempts within 15 minutes. The pam_faillock.so module (already in the default PAM stack on most distributions) reads this configuration.
Be careful with PAM
PAM misconfiguration can lock everyone - including root - out of a system. Always keep a separate root session open when editing PAM files, and test changes in a second terminal before closing your safety session.
Auditing Users¶
# Show current user's UID, GID, and groups
id
# Show another user's identity
id jdoe
# Show just the username
whoami
# Show who is currently logged in
who
# Show who is logged in and what they're doing
w
# Show recent login history
last
# Show last login time for all users
lastlog
# Query the name service (works with LDAP/SSSD too)
getent passwd jdoe
getent group developers
# Find all files owned by a user
find / -user jdoe -type f 2>/dev/null
# Find processes running as a user
ps -u jdoe
The getent command is more reliable than reading /etc/passwd directly because it queries all configured name sources, including LDAP and SSSD. If your organization uses centralized directory services, getent shows the full picture while cat /etc/passwd only shows local accounts.
Further Reading¶
- useradd man page - user account creation reference
- usermod man page - user account modification reference
- shadow(5) man page - /etc/shadow file format
- sudoers man page - sudo configuration reference
- Linux-PAM Documentation - PAM module reference and guides
- Arch Wiki: Users and groups - comprehensive practical reference
- NIST Password Guidelines (SP 800-63B) - modern password policy recommendations
Previous: System Services | Next: SSH Configuration | Back to Index