Linux Users & Groups — The Complete Access Control Guide
A new developer joins your team on Monday. They need SSH access to three servers, permission to deploy to staging, read-only access to production logs, and sudo for a handful of commands. Do you give them full root access and hope for the best? Absolutely not. Here's how to do it right.
How Linux Identifies Users
Every user on a Linux system has a numeric User ID (UID) and belongs to at least one group (GID). The kernel doesn't care about usernames — it only knows UIDs.
| UID Range | Type | Examples |
|---|---|---|
| 0 | Root | root — the superuser |
| 1-999 | System/service accounts | www-data, nginx, nobody |
| 1000+ | Regular users | vivek, deploy, jenkins |
# Check your own identity
whoami
id
# uid=1000(vivek) gid=1000(vivek) groups=1000(vivek),27(sudo),999(docker)
# Check another user
id deploy
# uid=1001(deploy) gid=1001(deploy) groups=1001(deploy),33(www-data)
The Critical Files
Three files control user authentication. Understanding their structure is essential.
/etc/passwd — User Account Info
# View the file (readable by everyone)
cat /etc/passwd | head -5
# root:x:0:0:root:/root:/bin/bash
# daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
# vivek:x:1000:1000:Vivek Goel:/home/vivek:/bin/bash
# Format: username:password:UID:GID:comment:home:shell
# The 'x' means password is stored in /etc/shadow
# Quick lookup for a specific user
getent passwd vivek
/etc/shadow — Password Hashes (root only)
# Only root can read this
sudo cat /etc/shadow | grep vivek
# vivek:$6$rounds=5000$salt$hash:19500:0:99999:7:::
# Fields: username:hash:last_change:min_age:max_age:warn:inactive:expire
# Check password aging for a user
sudo chage -l vivek
# Last password change : Feb 25, 2025
# Password expires : never
# Account expires : never
/etc/group — Group Definitions
cat /etc/group | grep -E "(sudo|docker|deploy)"
# sudo:x:27:vivek
# docker:x:999:vivek,deploy
# deploy:x:1001:
# Format: groupname:password:GID:member_list
Creating Users — The Right Way
# Create a regular user with home directory and bash shell
sudo useradd -m -s /bin/bash -c "New Developer" newdev
# Create and set password in one flow
sudo useradd -m -s /bin/bash newdev
sudo passwd newdev
# Create a system user (for running services)
# No home directory, no login shell, low UID
sudo useradd -r -s /usr/sbin/nologin -c "App Service" myapp
# Create user with specific UID and groups
sudo useradd -m -s /bin/bash -u 1500 -G sudo,docker,deploy -c "Senior Dev" seniordev
# Create user with an expiry date (contractor access)
sudo useradd -m -s /bin/bash -e 2025-06-30 -c "Contractor" contractor
| Flag | Purpose | Example |
|---|---|---|
-m | Create home directory | /home/username |
-s | Set login shell | /bin/bash or /usr/sbin/nologin |
-c | Comment (full name) | "Vivek Goel" |
-G | Supplementary groups | sudo,docker |
-g | Primary group | developers |
-u | Specific UID | 1500 |
-e | Expiry date | 2025-12-31 |
-r | System account | Low UID, no home |
Modifying Users
# Add user to additional groups (without removing existing ones)
sudo usermod -aG docker vivek
sudo usermod -aG sudo,deploy newdev
# CRITICAL: Always use -aG (append). Without -a, it REPLACES all groups!
# This is WRONG — removes user from all other groups:
# sudo usermod -G docker vivek # DON'T DO THIS
# Change user's shell
sudo usermod -s /bin/zsh vivek
# Lock an account (disable login)
sudo usermod -L baduser
# or
sudo passwd -l baduser
# Unlock an account
sudo usermod -U baduser
# Change username
sudo usermod -l newname oldname
# Set account expiry
sudo usermod -e 2025-06-30 contractor
# Move home directory
sudo usermod -d /home/newhome -m username
Managing Groups
# Create a new group
sudo groupadd developers
sudo groupadd -g 2000 devops # With specific GID
# Add users to the group
sudo usermod -aG developers newdev
sudo usermod -aG developers seniordev
# Remove a user from a group
sudo gpasswd -d newdev developers
# Delete a group
sudo groupdel oldgroup
# List all groups a user belongs to
groups vivek
# vivek : vivek sudo docker deploy developers
# List all members of a group
getent group developers
# developers:x:2000:newdev,seniordev
Deleting Users Safely
# Remove user but keep their home directory (safe)
sudo userdel contractor
# Remove user AND their home directory
sudo userdel -r contractor
# Before deleting, find files owned by the user
sudo find / -user contractor -ls 2>/dev/null
# Transfer ownership of their files
sudo find /opt/projects -user contractor -exec chown deploy:developers {} \;
# Full offboarding script
#!/bin/bash
USER=$1
echo "Offboarding user: $USER"
# 1. Disable the account immediately
sudo usermod -L $USER
sudo usermod -s /usr/sbin/nologin $USER
# 2. Kill any active sessions
sudo pkill -u $USER
# 3. Backup their home directory
sudo tar -czf /backup/offboarded_${USER}_$(date +%F).tar.gz /home/$USER
# 4. Remove from all groups
for grp in $(groups $USER | cut -d: -f2); do
sudo gpasswd -d $USER $grp 2>/dev/null
done
# 5. Remove the user (keep home for now)
sudo userdel $USER
echo "Done. Home directory backup at /backup/"
sudo — Controlled Privilege Escalation
Never give users the root password. Use sudo to grant specific elevated privileges.
# Edit sudoers safely (syntax check on save)
sudo visudo
# Common sudoers entries:
# Give user full sudo access (with password)
# vivek ALL=(ALL:ALL) ALL
# Give user full sudo without password
# deploy ALL=(ALL) NOPASSWD: ALL
# Allow specific commands only
# monitor ALL=(ALL) NOPASSWD: /usr/bin/systemctl status *, /usr/bin/journalctl
# Allow a group to run specific commands
# %developers ALL=(ALL) /usr/bin/docker, /usr/bin/docker-compose
# Drop-in files (better than editing main sudoers)
sudo visudo -f /etc/sudoers.d/developers
# %developers ALL=(ALL) NOPASSWD: /usr/bin/docker, /usr/bin/kubectl
# Check what sudo privileges you have
sudo -l
# Run command as another user
sudo -u www-data whoami
sudo -u deploy ls /opt/deploy/
# Open a root shell (use sparingly)
sudo -i
Best practice: Use drop-in files in /etc/sudoers.d/ instead of editing the main /etc/sudoers. Each team or role gets its own file. Much easier to manage and audit.
PAM — Pluggable Authentication Modules
PAM controls how Linux authenticates users. It sits between applications (like login, sshd, sudo) and authentication methods (passwords, keys, 2FA).
# PAM configuration lives here
ls /etc/pam.d/
# common-auth common-password login sshd sudo su
# View SSH authentication rules
cat /etc/pam.d/sshd
# Common PAM modules:
# pam_unix.so — Traditional password authentication
# pam_google_authenticator.so — 2FA with Google Authenticator
# pam_faillock.so — Lock accounts after failed attempts
# pam_limits.so — Resource limits per user
# Set resource limits per user
cat /etc/security/limits.conf
# deploy hard nofile 65535
# deploy hard nproc 4096
# @developers soft nofile 8192
# Lock account after 5 failed login attempts
# Add to /etc/pam.d/common-auth:
# auth required pam_faillock.so preauth deny=5 unlock_time=900
# auth required pam_faillock.so authfail deny=5 unlock_time=900
# Check failed login attempts
sudo faillock --user newdev
# Reset failed login counter
sudo faillock --user newdev --reset
Real-World Scenario: Team Onboarding Script
Here's a production-ready script for onboarding a new team member:
#!/bin/bash
# onboard-user.sh — Set up a new team member
set -euo pipefail
USERNAME=$1
FULLNAME=$2
ROLE=${3:-developer} # developer, devops, or readonly
echo "=== Onboarding: $FULLNAME ($USERNAME) as $ROLE ==="
# Create user
sudo useradd -m -s /bin/bash -c "$FULLNAME" "$USERNAME"
# Set temporary password (force change on first login)
TEMP_PASS=$(openssl rand -base64 12)
echo "${USERNAME}:${TEMP_PASS}" | sudo chpasswd
sudo chage -d 0 "$USERNAME"
# Assign groups based on role
case $ROLE in
developer)
sudo usermod -aG developers,docker "$USERNAME"
;;
devops)
sudo usermod -aG developers,docker,sudo,deploy "$USERNAME"
;;
readonly)
sudo usermod -aG monitoring "$USERNAME"
;;
esac
# Set up SSH directory
sudo mkdir -p /home/${USERNAME}/.ssh
sudo chmod 700 /home/${USERNAME}/.ssh
sudo touch /home/${USERNAME}/.ssh/authorized_keys
sudo chmod 600 /home/${USERNAME}/.ssh/authorized_keys
sudo chown -R ${USERNAME}:${USERNAME} /home/${USERNAME}/.ssh
echo "=== User created ==="
echo "Username: $USERNAME"
echo "Temp password: $TEMP_PASS"
echo "Role: $ROLE"
echo "Groups: $(groups $USERNAME)"
echo "IMPORTANT: User must change password on first login"
Run it:
sudo bash onboard-user.sh jdoe "Jane Doe" devops
Next up: Linux Networking Commands Every Engineer Should Know — your app can't connect to the database. Here's how to debug it step by step.
