Skip to main content

Linux Users & Groups — The Complete Access Control Guide

· 7 min read
Goel Academy
DevOps & Cloud Learning Hub

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 RangeTypeExamples
0Rootroot — the superuser
1-999System/service accountswww-data, nginx, nobody
1000+Regular usersvivek, 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
FlagPurposeExample
-mCreate home directory/home/username
-sSet login shell/bin/bash or /usr/sbin/nologin
-cComment (full name)"Vivek Goel"
-GSupplementary groupssudo,docker
-gPrimary groupdevelopers
-uSpecific UID1500
-eExpiry date2025-12-31
-rSystem accountLow 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.