Users, Groups & Permissions
Overview
Linux is a multi-user operating system built around a permissions model that controls what each user and process can access. Understanding user accounts, group membership, file ownership, and permission bits is essential for securing systems and debugging access issues.
User management
Listing and inspecting users
These commands reveal who you are, who is logged in, and what an account is allowed to do. id shows the UID, GID, and all group memberships at a glance.
whoami # current user
id # UID, GID, and group memberships
id www-data # inspect another user
who # logged-in users
w # logged-in users with what they're doing
last # recent login history
last -n 10 # last 10 logins
lastb # failed login attempts (requires /var/log/btmp)
User account files
User and group definitions live in plain-text files under /etc. Each file serves one purpose: account definitions, password hashes, groups, valid shells, and the skeleton copied into new home directories.
/etc/passwd # user account definitions
/etc/shadow # encrypted passwords and aging
/etc/group # group definitions
/etc/gshadow # group passwords (rarely used)
/etc/shells # valid login shells
/etc/skel/ # skeleton files for new user homes
/etc/passwd format:
username:password:UID:GID:GECOS:home_directory:shell
The password field is x (shadow passwords stored in /etc/shadow).
Creating and modifying users
useradd creates accounts and usermod adjusts them — adding groups, changing shells, locking accounts, and setting expiration dates. passwd manages passwords, and userdel removes accounts.
# Create a user
useradd -m -s /bin/bash jdoe # create with home dir and shell
useradd -u 1500 -g developers alice # specific UID and primary group
useradd -G docker,sudo bob # supplementary groups
# Modify a user
usermod -aG docker jdoe # add to supplementary group (append)
usermod -s /bin/zsh jdoe # change login shell
usermod -L jdoe # lock account
usermod -U jdoe # unlock account
usermod -e 2026-12-31 jdoe # set account expiration
# Delete a user
userdel jdoe # remove user (keep home dir)
userdel -r jdoe # remove user and home directory
# Password management
passwd jdoe # set/change password
passwd -l jdoe # lock password
passwd -u jdoe # unlock password
passwd -S jdoe # password status info
passwd -e jdoe # expire password (force change on next login)
# Change user info
chsh -s /bin/zsh # change own shell
chfn # change GECOS info (full name, phone, etc.)
Group management
Groups bundle users together so permissions can be granted to a whole team at once. groupadd, groupmod, and groupdel create, rename, and remove them, while groups and getent list memberships.
# Create and modify groups
groupadd developers # create a new group
groupadd -g 2000 ops # with specific GID
groupmod -n devs developers # rename group
groupdel developers # delete group
# List groups
groups # groups for current user
groups jdoe # groups for specific user
getent group # all groups on the system
getent group docker # specific group entry
# Change primary group
usermod -g developers jdoe
# Set group password (rare)
gpasswd developers
Adding users to groups
Membership in supplementary groups is what grants access to shared resources. Use -aG (append) with usermod so you add groups without clobbering existing memberships.
usermod -aG docker,www-data jdoe # append to supplementary groups
gpasswd -a jdoe docker # alternative syntax
gpasswd -d jdoe docker # remove from group
Changes take effect on next login. Use newgrp docker or su - jdoe in the current session to pick up new memberships immediately.
File permissions
Standard permission model
Permissions are represented as three triads: owner, group, and others.
-rwx r-x r-- owner group file.txt
│ │ │ │
│ │ │ └── others: read only
│ │ └────── group: read + execute
│ └────────── owner: read + write + execute
└───────────── file type: - (regular), d (directory), l (symlink)
Permission values
| Octal | Binary | Permissions |
|---|---|---|
| 0 | --- | None |
| 1 | --x | Execute only |
| 2 | -w- | Write only |
| 3 | -wx | Write + Execute |
| 4 | r-- | Read only |
| 5 | r-x | Read + Execute |
| 6 | rw- | Read + Write |
| 7 | rwx | Read + Write + Execute |
Changing permissions
chmod changes mode bits using either symbolic triads (u+x) or octal numbers (755). The two forms cover everything from quick tweaks to exact recursive permission sets.
# Symbolic mode
chmod u+x script.sh # owner: add execute
chmod g-w file.txt # group: remove write
chmod o= file.txt # others: remove all permissions
chmod a+r file.txt # all (ugo): add read
chmod u=rwx,g=rx,o= file.txt # set exact permissions
# Octal mode
chmod 755 script.sh # rwx r-x r-x
chmod 644 file.txt # rw- r-- r--
chmod 600 id_rsa # rw- --- ---
chmod 750 /opt/app # rwx r-x ---
# Recursive
chmod -R 755 /var/www # change all files and dirs
Changing ownership
chown changes the owner and group of a file or directory; chgrp changes only the group. Use -R to apply the change recursively.
chown jdoe file.txt # change owner
chown jdoe:developers file.txt # change owner and group
chown :developers file.txt # change group only
chown -R jdoe:developers /opt/app # recursive
chgrp developers file.txt # change group (alternative)
Special permission bits
| Bit | Octal | Symbol | File effect | Directory effect |
|---|---|---|---|---|
| SUID | 4000 | u+s | Execute as file owner | (no effect) |
| SGID | 2000 | g+s | Execute as group owner | New files inherit directory group |
| Sticky | 1000 | +t | (no effect) | Only file owner can delete |
chmod u+s /usr/bin/passwd # SUID: run as root regardless of caller
chmod g+s /shared/project # SGID: new files inherit directory group
chmod +t /tmp # Sticky: users can only delete own files
In ls output:
- SUID:
rwsin owner execute position (orrwSif no execute) - SGID:
rwsin group execute position (orrwSif no execute) - Sticky:
rwtin others execute position (orrwTif no execute)
Default permissions: umask
The umask subtracts permission bits from the defaults (666 for files, 777 for directories) so new files are created with sane, restricted permissions.
umask # display current mask (e.g., 0022)
umask 027 # set mask: new files get 640, dirs get 750
# Default file: 666 - 022 = 644 (rw-r--r--)
# Default dir: 777 - 022 = 755 (rwxr-xr-x)
Common umask values: 022 (group/others read), 027 (group read, others none), 077 (only owner).
Resolving path permissions with namei
namei follows a pathname until a terminal point is found, showing the permissions (and symlink targets) of every component along the way. This is invaluable when you need to understand why a user cannot access a file deep inside a directory tree.
# Show permissions and ownership at each level of a path
namei -l /var/www/html/index.html
# -l: long listing (permission bits, owner, group)
# Machine-readable output (colon-separated fields)
namei -m /var/log/app/debug.log
# Follow most symbolic links (default)
namei -l /usr/local/bin/myscript
# Do not follow symlinks — show them as symlinks
namei -n /usr/local/bin/myscript
Output columns (long format):
f: /var/www/html/index.html
drwxr-xr-x root root /
drwxr-xr-x root root var
drwxr-xr-x root root www
drwxrwx--- www www html
-rw-r--r-- www www index.html
The first column (f: or d: or l:) tells you the type. If a component returns "Permission denied", you know exactly which directory in the chain is blocking access.
Access Control Lists (ACLs)
ACLs extend the standard owner-group-others model with fine-grained permissions for specific users and groups.
# View ACLs
getfacl file.txt # show ACL for file
# Set ACLs
setfacl -m u:alice:rw file.txt # grant user alice read+write
setfacl -m u:alice:r-x script.sh # grant user alice read+execute
setfacl -m g:devs:r-- file.txt # grant group devs read
setfacl -m o::-- file.txt # remove all others permissions
setfacl -x u:alice file.txt # remove ACL for user alice
setfacl -b file.txt # remove all extended ACLs
# Default ACLs (inherited by new items in a directory)
setfacl -m d:u:alice:rwx /shared/
Requires ACL support: The filesystem must be mounted with the acl option. Check with tune2fs -l /dev/sda1 | grep "Default mount options".
Sudo configuration
sudo allows permitted users to execute commands as another user (typically root).
Configuration files
sudo is configured in /etc/sudoers, which should only be edited with visudo. Drop-in files under /etc/sudoers.d/ keep custom rules separate from the main file.
/etc/sudoers # main configuration (edit with visudo)
/etc/sudoers.d/ # drop-in configuration files
sudoers syntax
Each sudoers line grants a user or group a set of commands on specific hosts, optionally run as another user and optionally without a password. The last matching rule wins, so put specific grants after broad ones.
# User privilege specification
jdoe ALL=(ALL:ALL) ALL # jdoe can run anything as anyone
# Group privilege specification
%developers ALL=(ALL) ALL # group developers gets full sudo
%ops ALL=/usr/bin/systemctl restart nginx # specific commands only
# No password
jdoe ALL=(ALL) NOPASSWD: ALL
# Run as specific user
jdoe ALL=(postgres) /usr/bin/psql
sudo commands
sudo runs a single command as root or another user. These common invocations cover running as a specific user, starting a root shell, listing your own permissions, and invalidating cached credentials.
sudo command # run as root
sudo -u postgres command # run as specific user
sudo -i # interactive root shell
sudo su - # same as above
sudo -l # list allowed commands
sudo -k # invalidate cached credentials (force password)
visudo # safely edit /etc/sudoers
Managing sudoers safely
Always use visudo to edit sudoers — it validates syntax before saving:
visudo # edit /etc/sudoers
visudo -f /etc/sudoers.d/custom # edit a drop-in file
AppArmor
AppArmor is a Mandatory Access Control (MAC) framework, default on Debian/Ubuntu, that restricts program capabilities via profiles.
Checking status
aa-status lists all loaded AppArmor profiles and whether each is in enforce or complain mode.
aa-status # show loaded profiles and enforcement status
apparmor_status # same information
Each profile is in one of these modes:
- Enforce: Policy is enforced; violations are blocked and logged.
- Complain: Policy is NOT enforced; violations are only logged.
Managing profiles
Profiles live in /etc/apparmor.d/ and can be switched between enforce and complain modes, unloaded, or regenerated interactively while the system runs.
# Profile files location
/etc/apparmor.d/ # profile definitions
# Set profile mode
aa-enforce /etc/apparmor.d/usr.bin.nginx # enforce profile
aa-complain /etc/apparmor.d/usr.bin.nginx # complain mode (learning)
# Disable a profile
apparmor_parser -R /etc/apparmor.d/usr.bin.nginx # unload profile
ln -s /etc/apparmor.d/usr.bin.nginx /etc/apparmor.d/disable/ # disable at boot
# Reload all profiles
systemctl reload apparmor
# Generate a new profile
aa-genprof /path/to/binary # interactive profile generation
Viewing denials
AppArmor denials are logged by the kernel. dmesg and the syslog files are the places to look when a program is being blocked.
dmesg | grep -i apparmor
grep apparmor /var/log/syslog
grep apparmor /var/log/kern.log
Common permission recipes
These ready-made combinations cover the most common secure setups: SSH keys, web directories served by www-data, shared group areas, and dedicated application users.
# Secure SSH private key
chmod 600 ~/.ssh/id_rsa
# Secure SSH directory
chmod 700 ~/.ssh
# Web server directory (readable by www-data group)
chown -R root:www-data /var/www
find /var/www -type d -exec chmod 750 {} \;
find /var/www -type f -exec chmod 640 {} \;
# Shared group directory with SGID
chown root:devs /shared/project
chmod 2770 /shared/project # SGID bit + rwx for owner and group
# Application owned by dedicated user
chown -R app:app /opt/myapp
chmod 750 /opt/myapp