Every "permission denied" error on a Linux system traces back to this model — worth understanding properly instead of reflexively running chmod 777.
The three permission types
- r (read) — view a file's contents, or list a directory's contents.
- w (write) — modify a file, or create/delete files within a directory.
- x (execute) — run a file as a program, or
cdinto a directory (directories needxto be traversable at all, which surprises people).
The three "who" categories
Every file has permissions for three separate groups: the owner (a single user), the group (a set of users), and others (everyone else). Running ls -la shows all nine permission bits at once:
$ ls -la script.sh
-rwxr-xr-- 1 daniel staff 1240 Jul 20 10:15 script.shReading rwxr-xr-- in three chunks of three: rwx (owner: read, write, execute), r-x (group: read, execute, no write), r-- (others: read only, no write, no execute).
chmod with numeric codes
Each permission has a value: r=4, w=2, x=1. Add them up per category to get a three-digit code.
| Combination | Value |
|---|---|
| rwx | 7 (4+2+1) |
| rw- | 6 (4+2) |
| r-x | 5 (4+1) |
| r-- | 4 |
chmod 755 script.sh # owner: rwx, group: r-x, others: r-x — typical for an executable script
chmod 644 file.txt # owner: rw-, group: r--, others: r-- — typical for a regular file
chmod 600 secrets.env # owner: rw-, group: none, others: none — typical for sensitive fileschmod with symbolic notation
For targeted changes without recalculating the whole number:
chmod +x script.sh # add execute for everyone
chmod u+w file.txt # add write for the owner (u = user/owner)
chmod g-w file.txt # remove write for the group
chmod o-rwx secrets.env # remove all permissions for othersu, g, o (and a for all) combined with +, -, = and the permission letters — useful when you want to change one thing without touching the rest.
chown — changing who owns the file
chmod controls what each category can do; chown controls who is in the owner/group categories in the first place.
chown daniel file.txt # change the owner
chown daniel:staff file.txt # change owner and group together
chown -R daniel:staff /var/www # recursive — apply to a directory and everything inside itA very common real-world case: a file gets created by a root-owned process (e.g., inside Docker, or via sudo) and a regular user can no longer edit it. chown back to the right user is usually the actual fix — not chmod 777, which papers over an ownership problem with an overly permissive one.
The rule of thumb
If you find yourself reaching for chmod 777 (or chmod -R 777 on a whole directory), stop and ask who specifically needs access, and use the narrowest chmod/chown combination that grants it. It's rarely more than a couple of extra seconds of thought, and it avoids leaving a door open that outlives the reason you opened it.
Permission errors inside a running container are a close cousin of this topic — see Docker Compose for local development for the volume-mounting patterns where host/container permission mismatches most often show up.
Special permission bits: setuid, setgid, and the sticky bit
Beyond the standard rwx model, three less-common bits handle specific edge cases worth recognizing when you see them:
chmod u+s /usr/bin/some-binary # setuid — runs as the file's owner, not the invoking user
chmod g+s /shared/project # setgid on a directory — new files inherit the directory's group
chmod +t /tmp # sticky bit — only the file's owner can delete/rename itsetuid is why passwd (owned by root, setuid-enabled) lets a regular user change their own password despite the password database itself being root-owned — the binary temporarily runs with the owner's (root's) privileges for that specific operation. setgid on a shared directory is genuinely useful for team environments — every new file created inside automatically inherits the directory's group, instead of each contributor's own default group, keeping shared files consistently group-accessible without everyone remembering to chgrp manually. The sticky bit is why /tmp is world-writable but users still can't delete each other's files in it — write access to the directory alone would normally allow deleting any file inside it, and the sticky bit specifically overrides that for exactly this shared, world-writable case.
umask: setting default permissions before files even exist
umask controls what permissions new files and directories get by default, before any explicit chmod is ever run — it's a subtractive mask applied against a base of 666 for files and 777 for directories:
umask # show the current mask, e.g. 022
umask 027 # set a stricter default — group gets read-only, others get nothingA umask of 022 (a common default) subtracts write access for group and others, so new files land at 644 and new directories at 755 automatically. Setting a stricter umask (like 027, common on shared/multi-user servers) means every new file starts more locked-down by default, without needing to remember to chmod each one individually after creation — the earlier note about 777 directories not cascading to new files inside them is really umask deciding those new files' starting permissions instead.
Common mistakes
- Running
chmod -R 777on an entire directory to fix one file's permission problem — grants far more access than needed, to far more files than needed, and the "temporary" fix is rarely revisited once the immediate error goes away. - Confusing a permission problem with an ownership problem. "Permission denied" after a file was created by
root(common inside Docker containers, or aftersudo) is usually an ownership issuechownfixes —chmodalone won't help if the real issue is that your user isn't in the right owner/group category at all. - Forgetting that changing a file's permissions doesn't cascade to files created inside a directory afterward. A directory set to
777doesn't mean every new file created inside it also gets777— new files get permissions based on the creating process's umask, not the parent directory's mode. - Setting
600on a file that a service running as a different user actually needs to read — a mistake that trades "too permissive" for "too restrictive" and just moves the debugging time from a security review to a broken deployment.
Related reading
- Essential Linux Commands Every Developer Should Know — shares tags: linux (same category).
- CI/CD Pipelines Explained: From Commit to Production — shares tags: devops.
- Understanding Cloud Cost Optimization Basics — shares tags: devops.
- Docker Compose for Local Development Environments — shares tags: devops.
- Git Hooks Explained: Automate Your Workflow — shares tags: devops.