Skip to content

Mastering File Permissions

💡
Before you start

You need a Linux machine where you can use sudo, and you should not practise on a server anyone depends on. The commands here change real system state. A spare machine, a virtual machine, or a cloud instance you can rebuild is the right place; a laptop you own is fine too. If the terminal itself is new to you, do Introduction to the Linux Terminal first — it takes about ten minutes and everything below assumes it. Every step tells you how to undo it.

What This Builds On

This tutorial assumes you already understand ls -l output, what r, w and x mean, and basic chmod. If any of that is unfamiliar, read the introductory permissions tutorial in the Terminal section first — everything here builds directly on it.

What follows is the part that causes real incidents: directory semantics, the special bits, the mask that silently decides every new file's permissions, and access control lists.

Directories Do Not Mean What You Think

The same three letters mean something completely different on a directory, and this is the single most common source of confusion.

  • r on a directory — you may list the names inside it. That is all
  • x on a directory — you may traverse it and access things inside if you already know their names. Without x, nothing below is reachable regardless of the permissions on those files
  • w on a directory — you may create and delete entries in it. Deleting a file is a write to its directory, not to the file. This is why someone can delete a read-only file they do not own
💡
The consequence worth remembering.

r-x lets someone browse. --x lets them pass through to a known path without being able to list what else is there — genuinely useful for a home directory that must be traversable but not enumerable.

Ownership

sudo chown alice file.txt              # change owner
sudo chown alice:developers file.txt   # owner and group
sudo chgrp developers file.txt         # group only
sudo chown -R alice:alice /srv/app     # recurse

Use -R deliberately. Recursive ownership changes on a system directory are a well-worn way to break a machine — and there is no undo.

The Special Bits

Three extra bits sit above the usual nine, written as a fourth leading digit.

  • setuid (4) — an executable runs as its owner, not as the user who ran it. This is how passwd lets you edit a root-owned file. It appears as s in the owner's execute position
  • setgid (2) — on an executable, runs with the group's privileges. On a directory, far more usefully, new files inside inherit the directory's group instead of the creator's
  • sticky bit (1) — on a directory, only the owner of a file may delete it, even if others can write to the directory. This is what makes /tmp safe
chmod g+s /srv/shared          # setgid: shared group ownership
chmod +t /srv/dropbox          # sticky: users can only delete their own
ls -ld /tmp                    # drwxrwxrwt — note the trailing t

A shared team directory that actually works:

sudo mkdir /srv/shared
sudo chgrp developers /srv/shared
sudo chmod 2770 /srv/shared    # rwx for owner+group, setgid, nothing for others
⚠️
setuid is a privilege-escalation primitive.

A setuid-root binary with a bug hands an attacker root. Never set it on scripts or on anything you compiled casually. Audit what already has it:

sudo find / -perm -4000 -type f 2>/dev/null

Expect a short, familiar list — sudo, passwd, su, mount. Anything unexpected, especially in /tmp or a home directory, deserves immediate investigation.

umask: What Decides New Files

You never choose the permissions of a file you create — the umask does, by subtracting from a base of 666 for files and 777 for directories.

umask            # typically 0022
umask 0077       # new files 600, new dirs 700 — private by default
  • 022 (common default) → files 644, directories 755 — everyone on the system can read your new files
  • 027 → files 640, directories 750 — group reads, others get nothing. A sensible default for a shared server
  • 077 → files 600, directories 700 — fully private

Setting umask in a shell affects that session only. For a persistent change, set it in the user's shell profile, or system-wide via /etc/login.defs and PAM.

ACLs: When Owner/Group/Other Is Not Enough

The classic model has exactly three subjects. When you need "this one extra person gets read access" without creating a group, use an access control list.

getfacl report.txt                       # view
setfacl -m u:alice:rw report.txt         # grant alice read+write
setfacl -m g:auditors:r report.txt       # grant a group read
setfacl -x u:alice report.txt            # remove alice's entry
setfacl -b report.txt                    # strip all ACLs

A file with ACLs shows a + at the end of its ls -l mode string. That marker is easy to miss, and it means ls -l alone no longer tells you who has access — always check getfacl when auditing.

Default ACLs make new files in a directory inherit a rule:

setfacl -d -m g:developers:rwx /srv/project
💡
Watch the mask.

getfacl shows a mask:: line that caps every named user and group entry. If an ACL "is not working", the mask is usually why — a chmod on the file can silently tighten it.

Auditing Permissions

find /srv -perm -o+w -type f 2>/dev/null      # world-writable files
find /srv -nouser -o -nogroup 2>/dev/null     # orphaned by a deleted account
find /home -name '.ssh' -perm /go+rwx          # SSH dirs others can reach
find / -perm -2000 -type f 2>/dev/null         # setgid binaries

World-writable files are the classic finding: anyone on the system can modify them, which for a script or config file means anyone can change what runs.

Common Mistakes

  • chmod 777 — never the right answer. It is what people reach for when a permission problem is not understood, and it grants every user on the machine write access
  • Recursive chmod on mixed contentchmod -R 644 strips x from directories and makes the whole tree unreachable. Use find … -type d -exec chmod 755 and -type f -exec chmod 644 separately
  • Forgetting the parent directory — a perfectly permissioned file inside a directory you cannot traverse is still unreachable
  • Loose SSH permissions~/.ssh must be 700 and private keys 600, or OpenSSH refuses to use them. That refusal is a feature, not a bug

Now Do It Yourself: Five Steps

Beyond read, write and execute there are three bits that decide who a program runs as and who may delete what. They are behind a large share of real Linux privilege problems. You will see each one, on files you create. Every output below is exactly what the system printed.

1
Find out why you cannot give a file away

Go: open a terminal, run cd, then mkdir -p practice and cd practice.

Do: run touch report.txt, then chown root report.txt with no sudo.

You should see: chown: changing ownership of 'report.txt': Operation not permitted. You own the file, yet you cannot hand it to someone else. Only root can, and that is deliberate: if you could give a file away, you could plant a file in another user's name and make them accountable for it.

If not: if it succeeds, you are running as root already — check with whoami. Note the asymmetry worth remembering: chmod needs only ownership, while chown needs root. That is why you can lock your own files down without asking anyone.

2
Look at a real setuid program on your own system

Go: same terminal. This step only looks; it changes nothing.

Do: run ls -l /usr/bin/passwd, then find /usr/bin -maxdepth 1 -perm -4000.

You should see: a mode of -rwsr-xr-x on passwd — note the s where the owner's x would be — and then a short list of similar programs, typically including newgrp and passwd. That s is setuid: the program runs as its owner, root, no matter who launches it. It is how you can change your own password even though the password file is root-only.

If not: an empty list is fine on a hardened or containerised system — some deliberately remove every setuid binary. The reason this matters: a setuid-root program with a bug hands root to whoever exploits it, which is why that find command is one of the first things a security audit runs.

3
Set the bits yourself and watch the letters change

Go: same folder.

Do: run these four commands.

touch tool
chmod 4755 tool
stat -c '%A %a' tool
chmod 755 tool

You should see: -rwsr-xr-x 4755, then the bit removed again. The fourth digit is the special one: 4 setuid, 2 setgid, 1 sticky. Everything you already know is still there — 755 is unchanged underneath.

If not: if you see -rwSr-xr-x with a capital S, the execute bit is missing underneath. A capital letter means the special bit is set but the underlying execute bit is not, so it does nothing — a lowercase s is active, an uppercase S is inert. That distinction is easy to miss and is worth reading carefully every time.

4
Understand the sticky bit by looking at /tmp

Go: same terminal.

Do: run stat -c '%A %a %n' /tmp, then make your own: mkdir -p shared && chmod 1777 shared && stat -c '%A %a' shared.

You should see: drwxrwxrwt 1777 /tmp, and then the identical mode on your own folder. The final t is the sticky bit. 777 would normally mean anyone can delete anything in there; sticky restricts deletion to the owner of each file. That is the only reason a world-writable /tmp is not chaos.

If not: if /tmp shows something else on your system, it may be a private per-service temporary directory — systemd does that deliberately. The lesson stands: whenever you find yourself typing 777 on a directory, the answer you actually want is almost always 1777, or better, narrower permissions and a group.

5
Use setgid to make a shared folder that stays shared

Go: same folder.

Do: run chmod 2775 shared, then stat -c '%A %a' shared.

You should see: drwxrwsr-x 2775 — an s in the group position this time. On a directory, setgid means every file created inside inherits the folder's group rather than the creator's. That is the standard fix for a team share where one person's files keep coming out unreadable to everyone else.

If not: if new files still get the wrong group, remember setgid only affects files created after it was set. Fix the existing ones separately with chgrp -R teamname shared. And check your umask — umask 077 produces -rw-------, which no group setting can override, because the group has no permission to inherit in the first place.

🎉
Check yourself before moving on

Without scrolling up: you see -rwSr--r-- on a file. Is the setuid bit doing anything? Answer: no. The capital S means setuid is set but the owner's execute bit is not, so there is nothing to run and the bit is inert. Lowercase s is active; uppercase is a warning sign that something was set incompletely.

Now do it without the page: survey your own machine for setuid programs outside /usr/bin — search /usr/sbin and /usr/local/bin the same way as step 2 — and for each one you do not recognise, look up what it is before assuming it belongs there. That survey is a real audit task, not an exercise.

Summary

  • Directory permissions differ from file permissionsw on a directory means delete
  • setgid on a directory is the clean way to run shared team folders
  • The sticky bit stops users deleting each other's files in shared spaces
  • setuid is dangerous — audit it with find / -perm -4000
  • umask decides every new file027 or 077 beats the usual 022 on a shared machine
  • ACLs add precision, and a + in ls -l means you must check getfacl
🎉
Two commands worth running now.

sudo find / -perm -4000 -type f 2>/dev/null to see every setuid binary on your system, and umask to see what your next file will be created as. Both answers are usually more interesting than expected.