Linux Permissions and Users
Linux inherits the classic Unix permission model: users, groups, and read / write / execute bits on three classes (user, group, other). It’s simple enough to memorise in five minutes, powerful enough for most cases, and extended by ACLs, capabilities, and MAC systems (SELinux / AppArmor) when you need more.
Users and groups
Everything runs as a user (UID), belongs to a primary group (GID), and optionally joins many supplementary groups.
The source of truth
Plain text files in /etc:
| File | Holds |
|---|---|
/etc/passwd | User records (UID, GID, home, shell) — world-readable |
/etc/shadow | Password hashes + aging — root-only |
/etc/group | Groups + membership |
/etc/gshadow | Group passwords (rarely used) |
/etc/sudoers + /etc/sudoers.d/* | Who can run what as whom |
Example /etc/passwd line:
alice:x:1000:1000:Alice Example:/home/alice:/bin/bash
│ │ │ │ │ │ │
user │ UID GID GECOS (desc) home login shell
password placeholder (x = in shadow)
The x in the second field means “see /etc/shadow” for the real hash.
Special UIDs
| UID | Who |
|---|---|
0 | root — bypasses all permission checks |
1-99 (Debian) / 1-999 (RHEL) | System users for daemons (no login shell, no home) |
1000+ | Human users |
65534 | nobody — the “no privileges” fallback |
The “service account” pattern
Daemons should not run as root. Standard pattern:
useradd --system --no-create-home --shell /usr/sbin/nologin myapp
chown -R myapp:myapp /var/lib/myapp
# run the service as User=myapp in the systemd unitThis is what distro packages do for nginx, postgres, etc. Replicate it for anything custom.
The permission bits
ls -l file
-rwxr-xr-- 1 alice devs 4096 Apr 24 12:00 file
│└─┬─┘└─┬─┘└┬┘
│ u g o
└── file type (- file, d dir, l symlink, c char, b block, s socket, p pipe)
Three classes × three bits:
| Class | Who |
|---|---|
| u ser | The owner |
| g roup | Members of the file’s group |
| o ther | Everyone else |
Three bits per class:
| Bit | On a file | On a directory |
|---|---|---|
| r ead | Read contents | List entries (ls) |
| w rite | Modify contents | Create / delete / rename entries |
| e xecute | Run as program | Traverse into it (enter with cd) |
The directory-execute bit is the trap: without it, you can’t even cd into the dir; with it but no read bit, you can open files inside if you know their names but can’t list them. A web-server user often gets --x on a path: walk through, but no listing.
Numeric (octal) shorthand
Each class is a digit 0–7:
r = 4 w = 2 x = 1
Sum the bits:
| rwx | Octal |
|---|---|
--- | 0 |
r-- | 4 |
rw- | 6 |
r-x | 5 |
rwx | 7 |
Common modes:
| Use | Mode | Literal |
|---|---|---|
| Private file | 600 | rw------- |
| Private key (SSH) | 600 | rw------- |
| Script | 755 | rwxr-xr-x |
| World-readable config | 644 | rw-r--r-- |
| Private dir | 700 | rwx------ |
| Shared dir | 755 | rwxr-xr-x |
/tmp-style dir | 1777 | rwxrwxrwt (sticky bit) |
Changing ownership and mode
chmod 755 file # set absolute mode (numeric)
chmod u+x,g-w file # modify specific bits (symbolic)
chmod -R 750 /srv/app # recursive
chown alice file # change owner
chown alice:devs file # owner and group
chown -R myapp:myapp /var/lib/myapp
chgrp devs file # change only groupSymbolic vs numeric
Symbolic (u+x, g-w, o=r) is diff-friendly — it changes only specified bits. Numeric (755) is absolute — it overwrites. Prefer symbolic when you’re tweaking; numeric when you want a known end-state.
The three “special” bits
Beyond rwx, there are three magic bits that sometimes matter:
setuid (s) — octal 4000
On an executable: when run, the process takes on the owner’s UID, not the caller’s. passwd is the classic example: regular users can change their own password, which requires writing /etc/shadow, which requires root — setuid root on /usr/bin/passwd makes it work.
ls -l /usr/bin/passwd
-rwsr-xr-x 1 root root 68208 … /usr/bin/passwd
^ s instead of xSecurity concern: setuid root on a program with a bug is a privilege-escalation vector. find / -perm -4000 to audit.
setgid (s) — octal 2000
On an executable: run as the file’s group. On a directory: new files inherit the directory’s group (useful for shared folders).
chmod g+s /srv/shared
# now every file created in /srv/shared is group=devssticky bit (t) — octal 1000
On a directory: only the file’s owner (or root) can delete the file, even if others have write on the directory. That’s how /tmp works — everyone can write there, but you can only delete your own stuff.
ls -ld /tmp
drwxrwxrwt 18 root root 4096 … /tmp
^ tumask — default permission mask
When you create a file, the mode is 0666 & ~umask; for a dir it’s 0777 & ~umask. Default umask is typically 022:
0666
& ~022 (= 0644)
-------
0644 → rw-r--r--
Check / set:
umask # show current (e.g. 0022)
umask 077 # tighter: rwx for owner only, nothing elseSystemd services: UMask= in the [Service] section.
sudo — the real privilege escalation
sudo lets a user run commands as another user (default: root), subject to policy in /etc/sudoers and files in /etc/sudoers.d/.
Editing sudoers
Always use visudo (or visudo -f /etc/sudoers.d/myfile). It parses the file before saving — a syntax error in sudoers can lock you out of root access.
Common policies
# Everyone in wheel gets full sudo
%wheel ALL=(ALL:ALL) ALL
# No password for a specific user (dangerous — use sparingly)
alice ALL=(ALL) NOPASSWD: ALL
# Let the deploy user restart exactly one service
deploy ALL=(root) NOPASSWD: /usr/bin/systemctl restart myapp.service
# Aliases for readable policy
Cmnd_Alias SAFE = /usr/bin/tail -f *, /usr/bin/journalctl -u *
readonly ALL=(root) NOPASSWD: SAFEUseful habits:
- Use
sudo -ifor an interactive root shell (loads root’s env). - Use
sudo !!to re-run the last command with sudo. - Check what you can do:
sudo -l. - Never
sudo chmod -R 777anything you don’t understand.
Groups in practice
Add a user to a group:
usermod -aG docker alice # -a = append (vital — without -a you REPLACE group list)After this the user needs to log out / log back in (or newgrp docker) for the change to apply.
Common “power” groups:
| Group | Typical rights |
|---|---|
wheel / sudo | Use sudo (distro-dependent) |
adm | Read /var/log/* |
docker | Talk to the Docker daemon (effectively root — be careful who you add) |
dialout | Serial ports |
disk | Raw block devices (≈ root) |
plugdev, video, audio | Hardware access for desktop users |
ACLs — when three classes aren’t enough
Sometimes “user / group / other” is too coarse. Solution: POSIX ACLs give per-user and per-group grants on top of the classic bits.
# Grant bob read+write to a file owned by alice:devs
setfacl -m u:bob:rw file
getfacl file
# Default ACL on a directory — applies to new files inside
setfacl -m d:u:bob:rw /srv/sharedEnable on ext4 if needed: mount with acl option (most distros do by default).
When ls -l shows a + at the end of the mode, an ACL is in effect:
-rw-rw-r--+ 1 alice devs 4096 … file
^ACL present
Capabilities — finer than setuid
Modern Linux splits root’s power into ~40 capabilities (e.g. CAP_NET_BIND_SERVICE to bind ports <1024, CAP_NET_RAW for raw sockets, CAP_SYS_ADMIN the omnibus one). You can grant a single capability to a binary instead of full setuid root:
setcap cap_net_bind_service=+ep /usr/local/bin/myapp
# now myapp can bind :80 as an unprivileged user
getcap /usr/local/bin/myappOr at the systemd level:
[Service]
AmbientCapabilities=CAP_NET_BIND_SERVICE
CapabilityBoundingSet=CAP_NET_BIND_SERVICEThis is how modern services bind low ports without ever being root.
SELinux / AppArmor — the layer above DAC
The classic bits are DAC (Discretionary Access Control) — the owner decides. MAC (Mandatory Access Control) sits above: even root can be blocked by policy.
- SELinux — default on RHEL-family. Label-based.
getenforce,setenforce 0|1,sestatus,audit2allow. - AppArmor — default on Debian/Ubuntu/SUSE. Path-based.
aa-status, profiles in/etc/apparmor.d/.
When something works as root but not for a user, and permissions look fine, SELinux/AppArmor is the usual culprit. Check the audit log:
# SELinux
ausearch -m avc -ts recent
# AppArmor
journalctl -k | grep apparmorSSH key auth — where permissions actually bite you
SSH is strict about file modes. These trip people up constantly:
chmod 700 ~/.ssh
chmod 600 ~/.ssh/id_rsa
chmod 600 ~/.ssh/authorized_keys
chmod 644 ~/.ssh/id_rsa.pub
chmod 644 ~/.ssh/known_hostsIf ~/.ssh is group-writable, sshd silently refuses to honour authorized_keys. /var/log/auth.log (or journalctl -u sshd) will show the reason.
Quick checklist — “why can’t X read this file?”
ls -lathe file and every parent directory. X needsxon each parent.id X— confirm their UID and group membership.- Getfacl the file if
+shown in mode. - Check the filesystem mount — is it read-only (
mount | grep)? Mounted withnosuid/noexec? - Owner might be a numeric UID (no matching
/etc/passwdentry) —ls -ln. - SELinux/AppArmor denial? Check audit logs.
- On NFS: UIDs must match (or use ID mapping / Kerberos).