Permissions
5 min read
Core idea
One owner, one group, everyone else — three bits each
Linux inherits its permission model from 1970s Unix, where the basic insight was that a multiuser computer needs some access control but cannot afford the complexity of full access-control lists for every file. The compromise is elegant: every file has exactly one owner (a user) and one group, plus three permission triplets — read, write, execute — for user (the owner), group (members of the file's group), and other (everyone else). Nine bits, three classes, three actions. That is the entire model.
It is not the most expressive permission system in the world, but it has lasted fifty years because it is small enough to reason about, fast enough to check on every syscall, and composable enough to express most real-world policies through groups and the setuid/setgid/sticky bits.
The shell exposes the model through three verbs
Three commands change permissions: chmod (change the mode bits), chown (change the owner and/or group), and umask (set the default permissions removed from newly created files). Two commands change identity: su (substitute user, start a shell as someone else) and sudo (run one command as someone else, after policy check). Together they implement the principle of least privilege: stay as yourself unless a specific task requires elevation, then elevate only for that task.
Why it matters
It is the security boundary every Linux service trusts
Every system service — your web server, your database, your SSH daemon — stays sandboxed because file permissions stop it from reading /etc/shadow or writing into /root. When permissions are wrong, the boundary is wrong: a world-writable /etc/passwd is game over; a world-readable private SSH key gives an attacker your identity. Understanding the model is not optional for anyone who runs a service on a Linux box.
It is the model that survived the Windows alternative
Windows for decades let users run as Administrator by default. The result was a generation of malware that simply inherited the user's full privileges. Modern Linux distributions (and now macOS, and even modern Windows via UAC) converged on the Unix pattern: regular accounts run with restricted privileges, and elevation requires deliberate, authenticated action through sudo. The reason this matters is operational, not theoretical — when a process is compromised, the permissions on the files it can touch determine how bad the day gets.
Key takeaways
Mental model
How the kernel resolves a permission check
When a process tries to read, write, or execute a file, the kernel asks a chain of three questions in order. The first match wins.
Octal as a packed triplet
Practical application
-
Read modes before changing them. Run
ls -l(orstat) before anychmodorchown. The current mode tells you what's already permitted and who already owns the file — half the time the right answer is "nothing to change." -
Use symbolic
chmodfor adjustments, octal for resets.chmod +x script.shadds execute to all three classes without touching the read/write bits.chmod 644 fileresets the entire mode. Reach for symbolic when modifying, octal when defining. -
Use
sudo, notsu -, by default.sudo commandruns one command as root and returns you to your own shell.sudo -iopens a root shell when you genuinely need many commands. Avoid logging in as root or running a longsu -session — every minute as root is a minute where a typo can wreck the system. -
For shared directories, set the setgid bit on the directory.
chmod 2775 /srv/sharedmakes new files inside inherit the group ownership of/srv/shared. Combined withumask 002for the group's members, this gives a working "team folder" without per-file ownership fixes. -
Audit setuid binaries periodically. Run
find / -perm -4000 -type f 2>/dev/nullto list every setuid program on the system. There should be a small, known set (passwd,sudo,ping, a handful of others). An unexpected setuid binary in a user-writable location is a likely backdoor. -
Never make secrets group- or world-readable. SSH keys, API tokens, and config files containing passwords should be
chmod 600(or400). Many tools refuse to use a key that is not — SSH itself will reject a 644 private key with a permissions-too-open error.
Example
Building a shared "team" directory step by step
Imagine two users alice and bob need to collaborate on files under /srv/team. The naïve approach — mkdir /srv/team && chmod 777 /srv/team — works but is wide open to every user on the box. The correct setup uses a group and the setgid bit.
# As root (or via sudo):
$ groupadd team
$ usermod -aG team alice
$ usermod -aG team bob
$ mkdir /srv/team
$ chown :team /srv/team # group owner becomes 'team'
$ chmod 2775 /srv/team # 2 = setgid; 7=rwx user, 7=rwx group, 5=r-x other
Now any file alice creates under /srv/team inherits the team group (because of setgid on the parent directory), and bob — who is in team — can read it. Outsiders can list the directory (mode 5 = r-x for other) but cannot write.
There's one remaining wart: alice's umask defaults to 022, which means files she creates are 644 (rw-r--r--), and bob cannot write to them even though he's in the group. The fix is to set umask 002 in alice's and bob's ~/.bashrc so newly created files are 664 (rw-rw-r--) and group writes are allowed.
This is the canonical setgid + umask pattern. It is what GitHub's repository file modes used internally for years, what most enterprise NFS shares use, and what every multi-developer build directory should use.
Why setuid on a script doesn't work the way you think
Suppose you write a Python script that needs to read a privileged log file, and you try:
$ sudo chown root /usr/local/bin/check-logs.py
$ sudo chmod 4755 /usr/local/bin/check-logs.py # 4 = setuid bit
You'd expect non-root users running check-logs.py to read the file with root's permissions. They cannot. Linux ignores the setuid bit on scripts (since the early 1990s) because the kernel's race between checking the setuid bit and the interpreter opening the file was unfixable in the presence of #!/usr/bin/env-style indirections.
The correct fix is one of: (a) wrap the script in a tiny C program that is itself setuid root; (b) grant sudo-rules to specific users running specific commands; or (c) use POSIX capabilities (setcap cap_dac_read_search+ep) to give the binary only the specific privilege it needs. The third option is now standard practice in modern Linux — set the file's individual capability instead of giving it the entire root identity.
Related lessons
Related concepts
- File Permissionslinked concept
- Principle of Least Privilegelinked concept
- Multiuser Systemslinked concept