Users, Groups & Access Control

Why This Matters

Imagine you just set up a shared Linux server for your team. Three developers need to deploy code, two interns need read-only access to logs, and one DBA needs access only to the database directory. Without a solid understanding of users, groups, and access control, you will either lock everyone out or -- worse -- give everyone root access and pray nothing breaks.

Every file, every process, every socket on a Linux system is owned by a user and associated with a group. The entire security model of Linux rests on this foundation. When a web server gets compromised, it is the user/group model that determines whether the attacker can read /etc/shadow or pivot to other services. When a junior admin accidentally runs rm -rf /, it is the permission model that decides how much damage actually happens.

This chapter gives you the complete picture: how Linux identifies users, how groups organize access, how sudo elevates privileges safely, and how PAM ties authentication together behind the scenes.

Try This Right Now

Open a terminal and run these commands to see your own identity:

# Who am I?
whoami

# Full identity -- UID, GID, and all groups
id

# All users on this system (just usernames)
cut -d: -f1 /etc/passwd

# All groups on this system
cut -d: -f1 /etc/group

# Which groups do I belong to?
groups

You will see output like:

$ id
uid=1000(alice) gid=1000(alice) groups=1000(alice),27(sudo),999(docker)

That single line tells you everything the kernel needs to make access decisions about you.


Understanding UIDs and GIDs

Every user on a Linux system is identified by a User ID (UID) -- a number, not a name. The username is just a human-friendly label mapped to that number in /etc/passwd. The kernel does not care about "alice"; it cares about UID 1000.

Similarly, every group has a Group ID (GID).

The UID Landscape

+----------------------------------------------------+
|  UID Range         |  Purpose                       |
|--------------------|--------------------------------|
|  0                 |  root (superuser)              |
|  1 - 999           |  System/service accounts       |
|  1000 - 60000      |  Regular (human) users         |
|  65534             |  nobody (unmapped/overflow)     |
+----------------------------------------------------+

Distro Note: The boundary between system and regular UIDs varies. Debian/Ubuntu typically use 1000+ for regular users. RHEL/Fedora also use 1000+. Older systems may use 500+. Check /etc/login.defs for the UID_MIN and UID_MAX values on your system.

Why Does This Matter?

System accounts (UID 1-999) exist to run services. The www-data user runs your web server, mysql runs your database. These accounts typically have:

  • No valid login shell (set to /usr/sbin/nologin or /bin/false)
  • No home directory (or a non-standard one)
  • No password

This is a deliberate security design: even if someone exploits your web server, they land in a restricted account that cannot log in interactively.

# See the shell assigned to www-data (if it exists)
grep www-data /etc/passwd

# See root's entry
grep ^root /etc/passwd

The Three Identity Files

Linux stores user and group information in three critical files. Let us examine each.

/etc/passwd -- The User Database

Despite its name, this file does NOT contain passwords (not anymore). It is world-readable and contains one line per user:

# Look at your own entry
grep "^$(whoami):" /etc/passwd

The format is seven colon-separated fields:

username:x:UID:GID:comment:home_directory:shell

Example:

alice:x:1000:1000:Alice Johnson:/home/alice:/bin/bash
FieldValueMeaning
usernamealiceLogin name
passwordxPassword is in /etc/shadow
UID1000Numeric user ID
GID1000Primary group ID
commentAlice JohnsonFull name / description (GECOS field)
home/home/aliceHome directory
shell/bin/bashDefault login shell

The x in the password field means "look in /etc/shadow instead." In ancient Unix, the encrypted password was stored right here in this world-readable file. That was a terrible idea.

/etc/shadow -- The Password Vault

This file stores the actual password hashes and is readable only by root:

# This will fail as a regular user
cat /etc/shadow

# This works
sudo cat /etc/shadow

Format:

username:$algorithm$salt$hash:last_changed:min:max:warn:inactive:expire:reserved

Example:

alice:$6$rANd0mSaLt$kQ8Xp...long_hash...:19500:0:99999:7:::

The password hash format tells you the algorithm:

  • $1$ -- MD5 (ancient, insecure)
  • $5$ -- SHA-256
  • $6$ -- SHA-512 (most common today)
  • $y$ -- yescrypt (newer, used by Debian 12+, Fedora 35+)
FieldMeaning
last_changedDays since Jan 1, 1970 that password was last changed
minMinimum days before password can be changed
maxMaximum days before password must be changed
warnDays before expiry to warn the user
inactiveDays after expiry before account is disabled
expireDate the account expires (days since epoch)

Think About It: Why is the password hash stored in a separate file from /etc/passwd? What would happen if any user on the system could read everyone's password hashes?

/etc/group -- The Group Database

# See all groups
cat /etc/group

# See groups you belong to
grep "$(whoami)" /etc/group

Format:

groupname:x:GID:member1,member2,member3

Example:

sudo:x:27:alice,bob
docker:x:999:alice
developers:x:1001:alice,bob,charlie

A user has one primary group (from /etc/passwd, GID field) and zero or more supplementary groups (listed in /etc/group). When you create a file, it is owned by your primary group by default.


Hands-On: Managing Users

Creating Users

# Create a new user with defaults
sudo useradd testuser1

# Create a user with all the options you typically want
sudo useradd -m -s /bin/bash -c "Test User Two" testuser2

The flags explained:

  • -m -- Create home directory
  • -s /bin/bash -- Set login shell
  • -c "Test User Two" -- Set the GECOS comment field

Distro Note: On Debian/Ubuntu, useradd does NOT create a home directory by default -- you need -m. On RHEL/Fedora, useradd creates a home directory by default. To be safe, always use -m explicitly.

# Verify the user was created
grep testuser2 /etc/passwd
id testuser2
ls -la /home/testuser2/

Setting Passwords

# Set a password for the new user
sudo passwd testuser2

You will be prompted to enter the password twice. The passwd command handles all the hashing and writes to /etc/shadow.

# Check the shadow entry (see that a hash now exists)
sudo grep testuser2 /etc/shadow

Creating System Users

System users are for running services, not for humans to log in:

# Create a system user for a hypothetical app
sudo useradd --system --no-create-home --shell /usr/sbin/nologin myapp

# Verify -- note the low UID
id myapp
grep myapp /etc/passwd

Modifying Users

# Change a user's shell
sudo usermod -s /bin/zsh testuser2

# Add a user to a supplementary group (KEEP existing groups with -a)
sudo usermod -aG sudo testuser2

# Change the user's comment
sudo usermod -c "Test Account" testuser2

# Lock an account (disable login without deleting)
sudo usermod -L testuser2

# Unlock an account
sudo usermod -U testuser2

WARNING: Using usermod -G WITHOUT -a will REMOVE the user from all other supplementary groups. Always use -aG to append to existing groups. This is one of the most common and dangerous mistakes in user management.

Deleting Users

# Delete user but keep their home directory
sudo userdel testuser1

# Delete user AND their home directory and mail spool
sudo userdel -r testuser2

WARNING: userdel -r permanently removes the user's home directory. In production, consider locking the account instead and archiving the home directory first.


Hands-On: Managing Groups

# Create a new group
sudo groupadd developers

# Create a group with a specific GID
sudo groupadd -g 2000 ops

# Add existing users to the group
sudo usermod -aG developers alice

# See group membership
getent group developers

# Remove a user from a group (use gpasswd)
sudo gpasswd -d alice developers

# Delete a group
sudo groupdel ops

Primary vs. Supplementary Groups

+--------------------------------------------------+
|  User: alice                                     |
|                                                  |
|  Primary Group: alice (GID 1000)                 |
|    - Assigned in /etc/passwd                     |
|    - Default group for new files                 |
|                                                  |
|  Supplementary Groups:                           |
|    - sudo (GID 27)     -- can use sudo           |
|    - docker (GID 999)  -- can use Docker          |
|    - developers (GID 1001)                       |
+--------------------------------------------------+

When alice creates a file, it is owned by alice:alice (user:primary_group). If she wants new files to be owned by the developers group, she can:

# Temporarily change primary group for this session
newgrp developers

# Now create a file -- it belongs to 'developers' group
touch /tmp/team-file
ls -l /tmp/team-file

Think About It: A developer says "I added myself to the docker group but I still get permission denied." What is the most likely cause? (Hint: group membership changes require a fresh login session.)


su vs. sudo: Elevating Privileges

su -- Switch User

su lets you become another user entirely. You need that user's password:

# Become root (need root's password)
su -

# Become another user
su - alice

# Run a single command as another user
su -c "whoami" alice

The - (or -l) flag is important: it gives you a full login shell with that user's environment. Without it, you keep your current environment, which can cause confusing path and variable issues.

sudo -- Superuser Do

sudo lets you run commands as root (or another user) using YOUR OWN password, if you are authorized:

# Run a single command as root
sudo apt update

# Open a root shell
sudo -i

# Run a command as a different user
sudo -u postgres psql

# See what you are allowed to do
sudo -l

su vs. sudo: When to Use Which

+-----------------------------------------------------------+
|  Feature          |  su                |  sudo              |
|-------------------|--------------------|---------------------|
|  Password needed  |  Target user's     |  Your own           |
|  Granularity      |  All or nothing    |  Per-command control |
|  Audit trail      |  Minimal           |  Full logging        |
|  Best for         |  Switching users   |  Admin tasks         |
+-----------------------------------------------------------+

In modern Linux administration, sudo is preferred almost universally. It provides better auditing (every sudo command is logged), does not require sharing the root password, and allows fine-grained control over what each user can do.


The sudoers File and visudo

The /etc/sudoers file controls who can use sudo and what they can do. NEVER edit it directly with a text editor -- always use visudo:

# Open sudoers for editing (uses your default EDITOR)
sudo visudo

visudo checks syntax before saving. A syntax error in sudoers can lock you out of sudo entirely.

sudoers Syntax

The basic format is:

who    where=(as_whom)    what

Examples:

# alice can run any command as any user on any host
alice   ALL=(ALL:ALL)   ALL

# bob can only restart nginx
bob     ALL=(root)      /usr/bin/systemctl restart nginx

# The %sudo group can run anything (the % means "group")
%sudo   ALL=(ALL:ALL)   ALL

# ops group can run any command without a password
%ops    ALL=(ALL)       NOPASSWD: ALL

# charlie can run specific commands without a password
charlie ALL=(root)      NOPASSWD: /usr/bin/systemctl restart myapp, /usr/bin/journalctl -u myapp

Drop-in Files

Rather than editing the main sudoers file, you can create files in /etc/sudoers.d/:

# Create a drop-in file for a team
sudo visudo -f /etc/sudoers.d/developers

Add content like:

%developers ALL=(root) /usr/bin/systemctl restart myapp, /usr/bin/tail -f /var/log/myapp/*
# Make sure the include directive exists in sudoers
sudo grep includedir /etc/sudoers
# You should see: @includedir /etc/sudoers.d

Distro Note: Debian/Ubuntu ship with the @includedir /etc/sudoers.d directive enabled by default. On RHEL/CentOS, it is also present but uses the older #includedir syntax (the # is NOT a comment here -- this is one of the strangest syntax decisions in Unix history).


Debug This: sudo Permission Problems

A user reports they cannot use sudo despite being in the sudo group:

# The user runs:
sudo whoami
# Output: alice is not in the sudoers file. This incident will be reported.

Diagnosis steps:

# 1. Check if the user is actually in the sudo group
id alice
# Look for "sudo" or "wheel" in the groups list

# 2. Did they log out and back in after being added?
# Group changes require a new session!

# 3. Check sudoers configuration
sudo visudo
# Look for: %sudo ALL=(ALL:ALL) ALL
# On RHEL: %wheel ALL=(ALL) ALL

# 4. Check for syntax errors in drop-in files
sudo visudo -c
# This checks ALL sudoers files for syntax errors

# 5. Check the log for details
sudo grep sudo /var/log/auth.log        # Debian/Ubuntu
sudo grep sudo /var/log/secure           # RHEL/Fedora

Distro Note: The admin group is called sudo on Debian/Ubuntu and wheel on RHEL/Fedora/Arch. The name goes back to the 1970s: "big wheel" was slang for someone with power.


PAM: Pluggable Authentication Modules

PAM is the framework that sits behind every authentication event on Linux. When you type your password at the login screen, when you su to another user, when you sudo -- PAM is handling it.

How PAM Works

+-----------------+     +--------+     +------------------+
|  Application    |---->|  PAM   |---->|  PAM Modules     |
|  (login, sudo,  |     |  Core  |     |  pam_unix.so     |
|   ssh, su)      |     |        |     |  pam_ldap.so     |
+-----------------+     +--------+     |  pam_google...so  |
                                       |  pam_limits.so   |
                                       +------------------+

Applications do not handle passwords themselves. They ask PAM, and PAM consults a chain of modules. This means you can change how authentication works (add two-factor, switch to LDAP) without modifying any application.

PAM Configuration

PAM config files live in /etc/pam.d/, one per service:

# See all PAM-aware services
ls /etc/pam.d/

# Look at the sudo PAM config
cat /etc/pam.d/sudo

A PAM config line has four fields:

type    control    module    [arguments]
  • type: auth (verify identity), account (check access), password (change password), session (setup/teardown)
  • control: required, requisite, sufficient, optional
  • module: The shared library (e.g., pam_unix.so)

Example (/etc/pam.d/sudo):

auth    required    pam_unix.so
account required    pam_unix.so
session required    pam_limits.so

You do not need to master PAM internals right now, but understanding that it exists and how it is structured will help immensely when you need to configure LDAP authentication, set up two-factor auth, or debug "why can't this user log in?"


Hands-On: Practical Scenarios

Scenario 1: Onboarding a New Developer

# Create the user
sudo useradd -m -s /bin/bash -c "Dave Chen" dchen

# Set an initial password
sudo passwd dchen

# Add to the developers group
sudo usermod -aG developers dchen

# Give limited sudo access
sudo visudo -f /etc/sudoers.d/developers
# Add: %developers ALL=(root) /usr/bin/systemctl restart myapp

# Verify everything
id dchen
sudo -l -U dchen

Scenario 2: Offboarding an Employee

# Lock the account immediately
sudo usermod -L jsmith

# Kill any running processes
sudo pkill -u jsmith

# Expire the account
sudo usermod --expiredate 1 jsmith

# Archive home directory before deletion
sudo tar czf /backups/jsmith-home-$(date +%Y%m%d).tar.gz /home/jsmith

# Remove the user and home directory
sudo userdel -r jsmith

# Check for orphaned files anywhere on the system
sudo find / -nouser -o -nogroup 2>/dev/null

Scenario 3: Restricting a Contractor to a Specific Directory

# Create the user with a restricted shell
sudo useradd -m -s /bin/rbash contractor1
sudo passwd contractor1

# Create a limited PATH
sudo mkdir /home/contractor1/bin
sudo ln -s /usr/bin/ls /home/contractor1/bin/
sudo ln -s /usr/bin/cat /home/contractor1/bin/

# Set the restricted PATH in their .bashrc
echo 'export PATH=$HOME/bin' | sudo tee /home/contractor1/.bashrc
sudo chown contractor1:contractor1 /home/contractor1/.bashrc
sudo chmod 444 /home/contractor1/.bashrc

Password Policies and Account Aging

# See password aging info for a user
sudo chage -l alice

# Force password change on next login
sudo chage -d 0 alice

# Set password to expire in 90 days
sudo chage -M 90 alice

# Set minimum 7 days between password changes
sudo chage -m 7 alice

# Set account to expire on a specific date
sudo chage -E 2025-12-31 contractor1

You can set system-wide defaults in /etc/login.defs:

grep -E "^(PASS_MAX_DAYS|PASS_MIN_DAYS|PASS_WARN_AGE|UID_MIN|UID_MAX)" /etc/login.defs

What Just Happened?

+------------------------------------------------------------------+
|  Chapter 9 Recap: Users, Groups & Access Control                 |
|------------------------------------------------------------------|
|                                                                  |
|  - Every user has a UID; every group has a GID.                  |
|  - UIDs 0-999 are system accounts; 1000+ are regular users.      |
|  - /etc/passwd stores user info (not passwords!).                |
|  - /etc/shadow stores password hashes (root-readable only).      |
|  - /etc/group maps group names to GIDs and members.              |
|  - useradd, usermod, userdel manage users.                       |
|  - groupadd, gpasswd manage groups.                              |
|  - sudo is preferred over su for administrative tasks.           |
|  - sudoers controls who can sudo; always edit with visudo.       |
|  - PAM handles all authentication behind the scenes.             |
|  - Group changes require logging out and back in.                |
|                                                                  |
+------------------------------------------------------------------+

Try This

Exercise 1: User Audit

Run awk -F: '$3 >= 1000 && $3 < 65534 {print $1, $3, $7}' /etc/passwd to list all regular users, their UIDs, and shells. Are there any with /bin/false or /usr/sbin/nologin? Why might a regular-range UID have a non-login shell?

Exercise 2: Group Design

Design a group structure for a team of 5 developers, 2 QA engineers, and 1 DBA. What groups would you create? Which sudo permissions would each group need? Write the sudoers rules.

Exercise 3: Shadow File Analysis

Run sudo awk -F: '{print $1, $3, $5}' /etc/shadow to see usernames, last-changed dates, and max days. Calculate the actual date each password was last changed (hint: the number is days since January 1, 1970 -- use date -d "1970-01-01 + N days").

Exercise 4: Lock and Investigate

Create a test user, set a password, lock the account with usermod -L, then examine /etc/shadow. What character was added to the password hash? Unlock it and check again.

Bonus Challenge

Write a bash script that takes a username as an argument and outputs a full "identity report": username, UID, GID, primary group name, all supplementary groups, home directory, shell, password status (locked/unlocked/no password), last password change, and account expiry date. Use only standard commands (id, passwd, chage, grep, awk).