Featured image

Table of Contents Link to heading

User Accounts and Access Control Link to heading

Linux is a multi-user operating system built on a principle of least privilege: every user has access only to what they explicitly need. User accounts enforce this through two mechanisms:

  1. Authentication — verifying who the user is (via password, SSH key, or other credential)
  2. Authorisation — determining what that user is allowed to do (via file permissions, group membership, and sudo rules)

Every user account has a unique User ID (UID). Every group has a unique Group ID (GID). The kernel tracks ownership and access rights by these numeric IDs; usernames and group names are human-readable aliases stored in database files under /etc/.

Knowing how these files are structured is operationally important — they determine who can log in, what groups they belong to, and what system access those groups permit. They are also the first place to inspect when troubleshooting access problems.

Why Direct Root Login Is Risky Link to heading

The root account (UID 0) has unrestricted access to every file and system call on the system. Logging in directly as root introduces several compounding risks:

  1. Session-wide blast radius — every background process, browser session, or script running in a root shell has full system access; a single compromised subprocess can do anything
  2. Error amplification — a mistaken rm -rf or chmod -R 777 as root has immediate, system-wide consequences with no guardrails
  3. No accountability — root actions are not attributed to a specific operator; in shared environments, it is impossible to audit who did what
  4. Forgetting to switch back — operators logged in as root for an administrative task sometimes continue non-administrative work in the same session

The recommended approach:

  • On distributions that disable the root account (Ubuntu, modern Debian): use sudo for individual privileged commands
  • On distributions with a root account enabled: use su - to switch when needed for a specific task, then return to a regular account immediately after

Switching Users: su Link to heading

su (substitute user) opens a new shell as another user. Without a username argument, it defaults to root.

# Switch to root (requires the root password)
su

# Switch to root with a full login shell (loads root's environment)
su -

# Switch to a specific user
su username

# Switch with full login shell (loads that user's environment)
su - username

# Execute a single command as another user without opening an interactive shell
su - username --command "id"
su - root --command "systemctl restart nginx"

The - (or --login) option is important: without it, su opens a shell in the current environment. With it, the new shell sources the target user’s login files (.profile, .bash_profile), sets the home directory correctly, and loads the full environment of that user — which is typically what you want.

After finishing privileged work, exit returns to the original user’s shell.

Privileged Command Execution: sudo Link to heading

sudo allows an authorised user to execute a specific command as root (or another user) without switching accounts. It is the preferred mechanism for privilege escalation on well-administered systems.

# Run a command as root
sudo systemctl restart nginx
sudo less /var/log/auth.log

# Edit a file as root with the user's default editor
sudo --edit /etc/fstab          # safer than sudo vi (avoids root shell)
sudo -e /etc/fstab              # shorthand

# Run as a specific user and group
sudo --user=www-data --group=webteam id

# Repeat last command with sudo (bash/zsh only)
sudo !!

# Open a root login shell (use sparingly)
sudo --login
sudo -i                         # shorthand

# Open a root shell in the current environment (not a login shell)
sudo --shell
sudo -s                         # shorthand

# Run as a specific user
sudo --login --user=postgres psql

# List what commands the current user is allowed to run
sudo --list
sudo -l                         # shorthand

Key differences from su:

  • sudo prompts for the current user’s password, not root’s — an important security distinction in environments with many administrators
  • Each sudo invocation is logged to /var/log/auth.log (Debian/Ubuntu) or /var/log/secure (RHEL) with the username, command, and timestamp — creating an audit trail
  • Access is controlled by /etc/sudoers (edited safely with visudo) — fine-grained rules can permit specific commands to specific users without granting full root access

Account Data Files Link to heading

/etc/passwd Link to heading

/etc/passwd defines basic account information for every user. It is world-readable — any user on the system can read it.

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

Example:

root:x:0:0:root:/root:/bin/bash
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
sysadmin:x:1001:1001:Sysadmin User:/home/sysadmin:/bin/bash

Fields:

  • username: Login name
  • x: Password placeholder — actual password hash is in /etc/shadow
  • UID: User ID number
  • GID: Primary group ID
  • comment: Full name or description (GECOS field)
  • home_directory: User’s home directory path
  • shell: Login shell (/bin/bash, /usr/sbin/nologin for service accounts)
# Look up a specific user's entry
grep sysadmin /etc/passwd
getent passwd sysadmin

# Count total user accounts
wc -l /etc/passwd

/etc/shadow Link to heading

/etc/shadow stores the actual password hashes and password ageing policy. It is readable only by root — this separation prevents regular users from obtaining password hashes for offline cracking.

Field Description
Username Matches the username in /etc/passwd
Password Hashed password (format: $algorithm$salt$hash). * or ! means the account is locked
Last Change Days since Jan 1, 1970 that password was last changed
Min Minimum days between password changes (0 = can change anytime)
Max Days until password must be changed (99999 = never expires)
Warn Days before expiry that the user is warned
Inactive Grace days after expiry before account is disabled
Expire Days since Jan 1, 1970 that account expires (empty = never)
Reserved Unused, reserved for future use
# View shadow file (requires root)
sudo grep sysadmin /etc/shadow
sudo getent shadow sysadmin

Password hash format: $6$salt$hash — the $6$ prefix indicates SHA-512, which is the current standard. $1$ is MD5 (legacy, weak), $5$ is SHA-256.

/etc/group Link to heading

/etc/group defines group accounts and their memberships. Each line is colon-delimited:

group_name:password_placeholder:GID:member_list

Example:

sudo:x:27:sysadmin,henry
docker:x:998:sysadmin
developers:x:1002:alice,bob,charlie
Field Description
Group Name Human-readable group name
Password Placeholder x — group passwords (rare) are stored in /etc/gshadow
GID Group ID number
User List Comma-separated list of users for whom this is a supplementary group

Note: A user’s primary group is defined in /etc/passwd (GID field), not in /etc/group. The /etc/group entries show supplementary (secondary) group memberships.

grep docker /etc/group          # who is in the docker group
getent group sudo               # sudo group members
groups username                 # all groups for a user

Querying Account Data with getent Link to heading

getent retrieves entries from Name Service Switch (NSS) databases — including local files (/etc/passwd, /etc/shadow, /etc/group) and network directory services (LDAP, NIS). It is the correct tool to use in environments where user accounts may be managed centrally rather than in local files.

getent passwd sysadmin          # user account info
getent shadow sysadmin          # password aging info (requires root)
getent group docker             # group membership
getent group                    # all groups
getent passwd 1001              # look up user by UID
getent hosts server01           # DNS/hosts lookup
getent services ssh             # service port number

getent

Account Types Link to heading

Type UID Range Characteristics
Root 0 Unrestricted system access; UID 0 grants superuser regardless of username
System accounts 1–499 (or 1–999) Created for services (nginx, postgres, www-data); no home directory; /sbin/nologin or /bin/false shell; * in shadow password field
Regular user accounts ≥ 500 (or ≥ 1000) Human users; have home directories and real shells

System accounts exist so that services run with a dedicated, minimal-privilege identity rather than as root. If a service is compromised, the attacker gains only the permissions of that service account — not root.

# List system accounts (no login shell)
grep "nologin\|false" /etc/passwd

# List human user accounts
awk -F: '$3 >= 1000 && $3 != 65534 {print $1}' /etc/passwd

Viewing User Identity: id Link to heading

id displays the current user’s UID, primary GID, and all supplementary group memberships:

id                          # current user: uid, gid, all groups
id username                 # another user's identity
id -u                       # UID only (numeric)
id -g                       # primary GID (numeric)
id -G                       # all GIDs (numeric, space-separated)
id -Gn                      # all group names
id -un                      # username only

id

id is useful for verifying that group membership changes have taken effect (requires logout/login or newgrp), confirming the effective user when running under sudo, and debugging permission denied errors.

Monitoring Active Sessions Link to heading

who and w Link to heading

who shows which users are currently logged in:

who                         # list logged-in users
who am i                    # only your own session
who -a                      # all information
who -a -H                   # with column headers

Output fields:

  1. Username — logged-in account
  2. Terminaltty = local console; pts/N = pseudo-terminal (SSH, tmux, screen)
  3. Login time — when the session started
  4. Location — hostname (remote login) or display number (GUI login); blank = local text console

w provides a superset of who — it also shows each user’s current process and idle time:

w                           # show logged-in users and their activity
w -h                        # omit header line
w username                  # show only a specific user

who

users Link to heading

users prints a space-separated list of currently logged-in usernames:

users                       # current logins from /var/run/utmp
users /var/log/wtmp         # historical logins from wtmp

Viewing Login History: last Link to heading

last reads /var/log/wtmp (a binary file) and displays historical login records — who logged in, from where, when, and for how long:

last                        # all login records
last -n 20                  # most recent 20 records
last -F -a                  # full timestamp, hostname at end
last username               # logins for a specific user
last username -i            # show IP address instead of hostname
last reboot                 # system reboot history
last shutdown               # system shutdown history
# Count logins per user in the last month
last | awk '{print $1}' | sort | uniq -c | sort -rn

last

For failed login attempts (requires root):

sudo lastb                  # reads /var/log/btmp — failed logins
sudo lastb -n 20            # most recent 20 failed attempts

lastb is particularly useful for identifying brute-force SSH attacks — a large number of failed root or common-username login attempts from a single IP is a clear signal.