User management

User Management Manual

Linux user accounts are the atomic unit of identity on the system. Every process runs as a user. Every file has an owner. Every access decision – read, write, execute – is made against a user identity and its group memberships. Understanding how to create, modify, lock, and destroy users is foundational sysadmin work.

Think of the user database as a cell’s membrane – it defines what is inside the system and what is outside, who gets through and at what privilege level. The kernel enforces boundaries; user management is how you define them.

The four distributions covered – Alpine, Arch, Ubuntu, and Rocky – all implement POSIX user management but differ in their default tools, shadow password handling, and in what ships by default. Alpine uses BusyBox utilities; the others use shadow-utils. The commands are mostly compatible; the differences are noted where they matter.


1. Core Files

Everything about users and groups is stored in four plain-text files. Know them.

FileContents
/etc/passwdUser accounts: username, UID, GID, home, shell
/etc/shadowHashed passwords and password policy per user
/etc/groupGroup definitions and memberships
/etc/gshadowGroup passwords and group admins (rarely used)

/etc/passwd Format

username:x:UID:GID:GECOS:home_dir:login_shell
  • x in the password field means the password is in /etc/shadow
  • GECOS is the comment field – typically full name, room, phone
  • UID 0 is root. UIDs 1–999 are typically system accounts. UIDs 1000+ are human users

/etc/shadow Format

username:hashed_pw:last_change:min_age:max_age:warn:inactive:expire:reserved
  • All values are in days since the Unix epoch (1970-01-01)
  • ! or * in the password field means the account is locked
  • !! means no password has ever been set

/etc/group Format

groupname:x:GID:member1,member2,member3

2. Installation

Alpine Linux

# shadow provides useradd/usermod/userdel on Alpine (BusyBox has adduser/deluser by default)
apk add shadow sudo

Arch Linux

# shadow-utils is installed by default
# sudo requires separate install
sudo pacman -S sudo

Ubuntu / Debian

# All tools are present by default
sudo apt install passwd sudo adduser

Rocky Linux / RHEL

# All tools are present by default
sudo dnf install shadow-utils sudo

3. Adding Users

useradd – Low-Level Tool

useradd is the low-level, POSIX-standard command. It does exactly what you specify – no more. You must explicitly set options for home directory creation, shell, etc.

Create a user with defaults (no home dir, no password, system default shell):

sudo useradd username

Create a user with a home directory:

sudo useradd -m username

Create a user with home directory, shell, and comment:

sudo useradd -m -s /bin/bash -c "Full Name" username

Create a user with a specific UID:

sudo useradd -m -u 1500 username

Create a user with a specific primary group:

sudo useradd -m -g groupname username

Create a user with multiple supplementary groups:

sudo useradd -m -G wheel,docker,git username

Create a user with a specific home directory path:

sudo useradd -m -d /srv/apps/myapp username

Create a user with an account expiry date:

sudo useradd -m -e 2026-12-31 username

Create a system user (no home dir, no login shell, UID < 1000):

sudo useradd -r -s /usr/sbin/nologin myservice
sudo useradd -r -s /bin/false myservice

Full example – production user with all options:

sudo useradd \
  -m \
  -d /home/dil \
  -s /bin/bash \
  -u 1100 \
  -g users \
  -G wheel,docker \
  -c "Dil Osaigbovo" \
  dil

adduser – Higher-Level Wrapper

adduser is an interactive wrapper over useradd. It is more forgiving and asks for input if options are not provided. Behaviour differs between Alpine/Debian and Arch/Rocky.

Ubuntu / Debian:

sudo adduser username
# Interactive: asks for password, full name, room, phone, etc.

Non-interactive with all options:

sudo adduser --home /home/username --shell /bin/bash --gecos "Full Name" username

Alpine Linux (BusyBox adduser):

adduser username
adduser -h /home/username -s /bin/ash username
adduser -D username                   # no password, non-interactive
adduser -S username                   # system user
adduser -u 1200 -G users username     # specific UID and group

Arch / Rocky:

# adduser is not present by default; use useradd

4. Setting and Managing Passwords

passwd

Set or change a user’s password (as root – can change any user):

sudo passwd username

Set your own password (no root needed):

passwd

Set a password non-interactively (for scripting):

echo "username:newpassword" | sudo chpasswd

Set a password using a hashed value directly:

# Generate a SHA-512 hash
openssl passwd -6 "mypassword"
python3 -c "import crypt; print(crypt.crypt('mypassword', crypt.mksalt(crypt.METHOD_SHA512)))"

# Apply the hash directly
sudo usermod -p '$6$salt$hashedvalue...' username

Lock a user’s password (prefix with ! in shadow, disables password auth):

sudo passwd -l username

Unlock a user’s password:

sudo passwd -u username

Expire a password immediately (force change at next login):

sudo passwd -e username

Delete a password (allows passwordless login – dangerous):

sudo passwd -d username

Show password status:

sudo passwd -S username
# Output: username P 2026-01-01 0 99999 7 -1
# P = has password, L = locked, NP = no password
# Fields: name, status, last_change, min, max, warn, inactive

chpasswd (batch password setting)

# From a file
echo "user1:password1" | sudo chpasswd
echo "user2:password2" | sudo chpasswd

# Multiple users at once
sudo chpasswd << EOF
user1:pass1
user2:pass2
user3:pass3
EOF

5. Password Policy (chage)

chage manages password ageing – how long a password is valid, how soon the user is warned, and when the account expires.

Show current password ageing info for a user:

sudo chage -l username

Set maximum password age (force change every N days):

sudo chage -M 90 username

Set minimum password age (prevent immediate re-change):

sudo chage -m 7 username

Set warning period (warn user N days before expiry):

sudo chage -W 14 username

Set inactivity period (lock account N days after password expires):

sudo chage -I 30 username

Set account expiry date:

sudo chage -E 2026-12-31 username

Remove account expiry:

sudo chage -E -1 username

Force password change at next login:

sudo chage -d 0 username

Full example – set a complete password policy:

sudo chage -M 90 -m 7 -W 14 -I 30 -E 2027-01-01 username

System-Wide Password Policy

Default password policy for new accounts is defined in /etc/login.defs:

# Key fields in /etc/login.defs
PASS_MAX_DAYS   90       # maximum password age
PASS_MIN_DAYS   7        # minimum password age
PASS_WARN_AGE   14       # days before warning
UID_MIN         1000     # minimum UID for human users
UID_MAX         60000    # maximum UID for human users
GID_MIN         1000
GID_MAX         60000
ENCRYPT_METHOD  SHA512   # password hashing algorithm
CREATE_HOME     yes      # create home by default
UMASK           022      # default umask

6. Modifying Users

usermod – All Modifications

Change username:

sudo usermod -l newname oldname

Change home directory (does not move files):

sudo usermod -d /new/home username

Change home directory and move files:

sudo usermod -d /new/home -m username

Change login shell:

sudo usermod -s /bin/zsh username
sudo usermod -s /usr/bin/fish username
sudo usermod -s /sbin/nologin username    # disable interactive login

Change UID:

sudo usermod -u 1500 username

Change primary group:

sudo usermod -g newgroup username

Add to supplementary groups (append – do not replace):

sudo usermod -aG docker username
sudo usermod -aG wheel,docker,sudo username

Set supplementary groups (replaces existing):

sudo usermod -G docker,git username

Change GECOS / comment field:

sudo usermod -c "New Full Name" username

Set account expiry date:

sudo usermod -e 2027-06-01 username

Remove account expiry:

sudo usermod -e "" username

Lock account:

sudo usermod -L username

Unlock account:

sudo usermod -U username

7. Deleting Users

userdel

Delete a user (keeps home directory and files):

sudo userdel username

Delete a user and their home directory:

sudo userdel -r username

Force delete even if the user is logged in:

sudo userdel -f username
sudo userdel -rf username     # force + remove home

deluser (Ubuntu / Alpine)

# Ubuntu
sudo deluser username
sudo deluser --remove-home username
sudo deluser --remove-all-files username   # removes all files owned by user

# Alpine (BusyBox)
deluser username

Cleanup After Deletion

After deleting a user, orphaned files remain on the filesystem – files owned by a now-nonexistent UID:

# Find all files on the system owned by a specific UID (run before deleting)
sudo find / -uid 1100 -ls 2>/dev/null

# Find orphaned files (owned by no current user)
sudo find / -nouser -ls 2>/dev/null

# Find orphaned directories
sudo find / -nogroup -ls 2>/dev/null

# Remove or reassign orphaned files
sudo find / -nouser -exec chown root:root {} \;

8. Groups

Creating Groups

sudo groupadd groupname
sudo groupadd -g 1500 groupname        # specific GID
sudo groupadd -r sysgroup              # system group (GID < 1000)

Modifying Groups

sudo groupmod -n newname oldname       # rename group
sudo groupmod -g 1600 groupname        # change GID

Deleting Groups

sudo groupdel groupname

You cannot delete a group that is the primary group of any user. Remove the users from it first or change their primary group.

Managing Group Membership

Add a user to a group:

sudo usermod -aG groupname username
sudo gpasswd -a username groupname

Remove a user from a group:

sudo gpasswd -d username groupname

Set the complete member list for a group:

sudo gpasswd -M user1,user2,user3 groupname

Set a group administrator (can add/remove members without sudo):

sudo gpasswd -A username groupname

Set a group password (rarely used):

sudo gpasswd groupname

Viewing Group Membership

Show groups a user belongs to:

groups username
id username

Show all members of a group:

getent group groupname
grep '^groupname:' /etc/group

List all groups:

cat /etc/group
getent group

9. Switching Users and Privilege Escalation

su – Switch User

Switch to root:

su -
su -l

Switch to another user (load their environment):

su - username

Run a single command as another user:

su -c "command" username
su -c "systemctl restart nginx" root

sudo – Delegated Root Access

Run a command as root:

sudo command

Run a command as a specific user:

sudo -u username command
sudo -u www-data ls /var/www

Open a root shell:

sudo -i          # login shell (loads root's environment)
sudo -s          # non-login shell
sudo su -        # full root shell via su

Run multiple commands as root in a subshell:

sudo bash -c 'command1 && command2'

Preserve the caller’s environment:

sudo -E command

List what the current user can do with sudo:

sudo -l

10. sudo Configuration

/etc/sudoers controls who can run what with sudo. Always edit it with visudo – it validates syntax before saving. A broken sudoers file locks you out of sudo.

sudo visudo

On systems using /etc/sudoers.d/, add per-user or per-group files instead:

sudo visudo -f /etc/sudoers.d/dil

sudoers Syntax

Grant full root access:

username ALL=(ALL:ALL) ALL

Grant access without password:

username ALL=(ALL) NOPASSWD: ALL

Grant access to a specific command only:

username ALL=(ALL) /usr/bin/systemctl restart nginx

Grant multiple specific commands:

username ALL=(ALL) NOPASSWD: /usr/bin/apt update, /usr/bin/apt upgrade

Grant a group full access:

%wheel ALL=(ALL:ALL) ALL
%sudo  ALL=(ALL:ALL) ALL

Restrict to specific hosts:

username webserver=(ALL) ALL

Wheel Group (Arch / Rocky)

Arch and Rocky use the wheel group for sudo access. It is commented out by default:

sudo visudo
# Uncomment the line:
%wheel ALL=(ALL:ALL) ALL

Then add the user to wheel:

sudo usermod -aG wheel username

sudo Group (Ubuntu)

Ubuntu uses the sudo group:

sudo usermod -aG sudo username

Alpine sudo

apk add sudo
# Add to wheel group
adduser username wheel
# Edit sudoers
visudo
# Uncomment: %wheel ALL=(ALL) ALL

11. Locking and Disabling Accounts

There are multiple ways to prevent a user from logging in. They are not equivalent.

Lock the Password

Prefixes ! to the password hash in /etc/shadow. Blocks password authentication but SSH key auth still works.

sudo passwd -l username
sudo usermod -L username

Unlock:

sudo passwd -u username
sudo usermod -U username

Set a Non-Login Shell

Blocks interactive login entirely (SSH included if UsePAM yes is set):

sudo usermod -s /sbin/nologin username
sudo usermod -s /bin/false username

/sbin/nologin displays a message before refusing. /bin/false silently exits. Neither allows interactive access.

Expire the Account

sudo chage -E 0 username             # expire immediately
sudo usermod -e 1970-01-02 username  # past date = expired

Lock SSH Access

In /etc/ssh/sshd_config:

DenyUsers username
DenyGroups badgroup

Or create ~/.ssh/authorized_keys with no keys and set chmod 600 so no keys can be added.

Check Lock Status

sudo passwd -S username
sudo chage -l username
sudo grep username /etc/shadow

12. User Information and Inspection

Show user account info:

id username                # UID, GID, groups
finger username            # detailed info (if `finger` installed)
getent passwd username     # entry from /etc/passwd (also queries LDAP/NIS)
getent shadow username     # shadow entry (root only)

Show all users:

cat /etc/passwd
getent passwd
awk -F: '$3 >= 1000 && $3 < 65534 {print $1}' /etc/passwd   # human users only

Show currently logged-in users:

who
w                          # more detailed -- what they're running
users                      # just usernames
last                       # login history
last username              # login history for a specific user
lastb                      # failed login attempts (root only)

Show last login time:

lastlog
lastlog -u username

13. PAM – Pluggable Authentication Modules

PAM is the authentication framework underlying all login operations. Understanding it at a basic level is necessary for advanced user management – password complexity, account lockout, session limits.

PAM config files live in /etc/pam.d/. Each service has its own file (sshd, login, sudo, passwd).

Password Complexity (pam_pwquality)

Install:

# Alpine
apk add linux-pam

# Arch
sudo pacman -S libpwquality

# Ubuntu
sudo apt install libpam-pwquality

# Rocky
sudo dnf install libpwquality

Configure in /etc/security/pwquality.conf:

minlen = 12              # minimum length
dcredit = -1             # at least 1 digit
ucredit = -1             # at least 1 uppercase
lcredit = -1             # at least 1 lowercase
ocredit = -1             # at least 1 special character
maxrepeat = 3            # no character repeated more than 3 times
usercheck = 1            # reject passwords containing username

Enable in /etc/pam.d/passwd (or common-password on Ubuntu):

password required pam_pwquality.so retry=3

Account Lockout After Failed Attempts (pam_faillock)

Install:

# Arch
sudo pacman -S pam

# Ubuntu
sudo apt install libpam-modules

# Rocky
sudo dnf install pam

Configure /etc/security/faillock.conf:

deny = 5                 # lock after 5 failures
unlock_time = 900        # unlock after 15 minutes
fail_interval = 300      # count failures within 5-minute window

Add to /etc/pam.d/system-auth (before pam_unix.so):

auth required pam_faillock.so preauth
auth [default=die] pam_faillock.so authfail
auth sufficient pam_unix.so
auth [default=die] pam_faillock.so authfail
account required pam_faillock.so

Check locked accounts:

sudo faillock
sudo faillock --user username

Manually unlock:

sudo faillock --user username --reset

Limit Simultaneous Sessions (pam_limits)

Configure /etc/security/limits.conf:

# Format: username  type  resource  value
dil        hard   nproc    50        # max processes
dil        soft   nofile   1024      # open files (soft)
dil        hard   nofile   65536     # open files (hard)
@developers soft  nproc    100       # group limit
*          hard   core     0         # disable core dumps for all

Enable in /etc/pam.d/system-auth:

session required pam_limits.so

14. Alpine-Specific Notes

Alpine uses BusyBox’s adduser/deluser by default. Install the shadow package to get full useradd/usermod/userdel compatibility.

apk add shadow

BusyBox adduser does not support all flags that shadow’s useradd does. In container environments (Incus/Docker on Alpine), prefer:

# Add a user in a Dockerfile/container init
adduser -D -H -s /sbin/nologin appuser      # no password, no home, no login
adduser -D -h /app -s /bin/sh appuser       # with home, ash shell

Alpine uses /etc/group and /etc/passwd normally. sudo on Alpine requires adding to the wheel group via BusyBox adduser:

adduser username wheel

15. Home Directory Management

Create a home directory manually for an existing user:

sudo mkdir -p /home/username
sudo cp -r /etc/skel/. /home/username/     # copy skeleton files
sudo chown -R username:username /home/username
sudo chmod 700 /home/username

/etc/skel/ contains the template files copied into every new home directory. Customise it to control what every new user starts with:

ls /etc/skel/
# .bash_profile  .bashrc  .profile  (and whatever you add)

Move a home directory:

sudo usermod -m -d /new/home/username username

16. Scripting Common Tasks

Bulk User Creation

#!/bin/bash
# create-users.sh
# Input file: users.txt -- one per line: username:password:groups:shell

while IFS=: read -r username password groups shell; do
     "$username" =~ ^#  && continue    # skip comments
    sudo useradd -m -s "$shell" -G "$groups" "$username"
    echo "$username:$password" | sudo chpasswd
    sudo chage -M 90 -W 14 "$username"
    echo "Created: $username"
done < users.txt

Audit All Users

#!/bin/bash
# List all human users with UID, groups, shell, last login
awk -F: '$3 >= 1000 && $3 < 65534 {print $1}' /etc/passwd | while read user; do
    uid=$(id -u "$user")
    groups=$(groups "$user" | cut -d: -f2 | xargs)
    shell=$(getent passwd "$user" | cut -d: -f7)
    last=$(lastlog -u "$user" | tail -1 | awk '{print $4, $5, $6, $9}')
    printf "%-15s UID:%-6s SHELL:%-15s GROUPS:%-30s LAST:%s\n" \
        "$user" "$uid" "$shell" "$groups" "$last"
done

Remove Inactive Users

#!/bin/bash
# Find users who haven't logged in for 180 days
CUTOFF=$(date -d '180 days ago' +%Y-%m-%d)
lastlog | awk 'NR>1 && $4 != "**Never" {print $1, $4, $5, $6, $9}' | while read user month day dow year; do
    last_login="${year}-$(date -d "$month $day" +%m-%d)"
    if  "$last_login" < "$CUTOFF" ; then
        echo "Inactive: $user (last: $last_login)"
    fi
done

17. Quick Reference

Add a Standard User

sudo useradd -m -s /bin/bash -G wheel,docker -c "Full Name" username
sudo passwd username

Add a Service Account (no login)

sudo useradd -r -s /sbin/nologin -d /var/lib/myapp -m myapp

Lock a User

sudo passwd -l username          # lock password
sudo usermod -s /sbin/nologin username   # block shell

Delete a User Completely

sudo userdel -r username
sudo find / -nouser -ls 2>/dev/null     # check for orphaned files

Grant sudo

# Arch / Rocky
sudo usermod -aG wheel username

# Ubuntu
sudo usermod -aG sudo username

Group Operations

sudo groupadd mygroup
sudo usermod -aG mygroup username
sudo gpasswd -d username mygroup
sudo groupdel mygroup

Password Policy

sudo chage -M 90 -m 7 -W 14 username
sudo chage -l username
sudo passwd -S username

  • 202603161901: Processes run under user identities – ownership matters for signal permissions.
  • 202604090000 Linux Storage Management 3 – Filesystems and Mounting#2. Mounting and Unmounting: Mount points respect user/group ownership. uid= and gid= are mount options.
  • 202604070826: Container users are mapped to host UIDs via subuid/subgid.