Skip to content

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:

jdoe:x:1001:1001:Jane Doe:/home/jdoe:/bin/bash
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:

jdoe:$6$rounds=5000$salt$hash:19800:0:99999:7:::
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:

developers:x:1002:jdoe,asmith,bwilson
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/:

# Create a rule for the developers group
sudo visudo -f /etc/sudoers.d/developers
# /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:

auth    required    pam_unix.so    nullok
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:

auth    required    pam_wheel.so

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:

deny = 5
unlock_time = 600
fail_interval = 900

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


Previous: System Services | Next: SSH Configuration | Back to Index

Comments