A deploy fails with “Permission denied.” A teammate cannot edit a shared config. A script refuses to run because it is not executable. Those problems usually come down to three questions: what mode does the path have?, who owns it?, and what mode do new files get by default? That is what chmod, chown, and umask are for. chmod sets the permission bits. chown sets the user and group. umask shapes the mode applied when you create a file or directory.
You do not need every mode on day one. Start by reading what is there with ls or stat, change only the paths you mean to change, and treat recursive updates as a deliberate last step. The sections below walk through the commands you will reach for most often — with enough context that each one feels intentional, not magical.
Warm-up: a disposable permissions playground
Build a small tree you can inspect and mutate safely. It mirrors a tiny project: a readable note, a private secret, a script you will make executable, and a nested directory.
Create it once, then reuse it for the examples:
mkdir -p demo-perms/{bin,private,shared}
echo 'hello world' > demo-perms/shared/notes.txt
echo 'top-secret' > demo-perms/private/secret.env
cat > demo-perms/bin/greet.sh <<'EOF'
#!/usr/bin/env bash
echo "hello from greet.sh"
EOF
Confirm the layout:
find demo-perms -print | sort
demo-perms
demo-perms/bin
demo-perms/bin/greet.sh
demo-perms/private
demo-perms/private/secret.env
demo-perms/shared
demo-perms/shared/notes.txt
Note: Practice only inside demo-perms (or another path you own). Do not run recursive chmod/chown against /, /etc, or someone else’s home while learning.
Read permissions first
Before changing anything, learn to see the mode and ownership. Long listing is the everyday view:
ls -l demo-perms/shared/notes.txt
Typical shape (owner, group, and timestamps will differ on your machine):
-rw-r--r-- 1 alice alice 12 Aug 5 01:00 demo-perms/shared/notes.txt
Read the ten-character mode string from left to right:
- Position 1: type (
-file,ddirectory,lsymlink, …) - Positions 2–4: user (owner)
rwx - Positions 5–7: group
rwx - Positions 8–10: other
rwx
So -rw-r--r-- means the owner can read and write, while group and other can only read. On directories, x means you may enter the directory (traverse to children); without it, you cannot cd in or resolve paths beneath it even if the files inside look readable.
For a pasteable one-liner that mixes octal, symbolic, and ownership:
stat -c '%a %A %U:%G %n' demo-perms/shared/notes.txt
644 -rw-r--r-- alice:alice demo-perms/shared/notes.txt
Walk the tree the same way:
find demo-perms -exec stat -c '%a %A %U:%G %n' {} \;
Note: Prefer stat (or ls -l) before every non-trivial chmod/chown. Confirming the current mode is cheaper than recovering from a recursive mistake.
Symbolic chmod: speak the change
Symbolic mode names who, what happens, and which bits:
| Piece | Meaning |
|---|---|
u / g / o / a | user, group, other, or all |
+ / - / = | add, remove, or set exactly |
r / w / x | read, write, execute |
Make the script runnable for its owner. Reset to a known 644 first so the result does not depend on your shell’s umask:
chmod 644 demo-perms/bin/greet.sh
chmod u+x demo-perms/bin/greet.sh
stat -c '%a %A %n' demo-perms/bin/greet.sh
744 -rwxr--r-- demo-perms/bin/greet.sh
u+x adds execute for the owner only (744). Use chmod 755 (or chmod a+x after 644) when group and other should run it too.
Lock down the secret so only the owner can read or write:
chmod go-rwx demo-perms/private/secret.env
# equivalent intent: chmod u=rw,go= demo-perms/private/secret.env
stat -c '%a %A %n' demo-perms/private/secret.env
600 -rw------- demo-perms/private/secret.env
Make notes world-readable but not writable by anyone else:
chmod a=r,u+w demo-perms/shared/notes.txt
stat -c '%a %A %n' demo-perms/shared/notes.txt
644 -rw-r--r-- demo-perms/shared/notes.txt
Note: = replaces the named class’s bits. chmod a=r clears write and execute for everyone; add u+w (or use octal 644) when the owner still needs to edit the file.
Numeric chmod: three octal digits
Octal mode is often clearer when you already know the target. Each digit is the sum of 4 (read) + 2 (write) + 1 (execute) for user, group, and other:
| Digit | Bits | Meaning |
|---|---|---|
7 | rwx | read, write, execute |
6 | rw- | read, write |
5 | r-x | read, execute |
4 | r-- | read only |
0 | --- | none |
Everyday values:
| Mode | Typical use |
|---|---|
644 | Normal file (owner edit; others read) |
755 | Executable or directory others may traverse |
600 | Private file (secrets, keys) |
700 | Private directory or owner-only script |
Reset the script and secret with octal so the intent is unambiguous:
chmod 755 demo-perms/bin/greet.sh
chmod 600 demo-perms/private/secret.env
stat -c '%a %A %n' demo-perms/bin/greet.sh demo-perms/private/secret.env
755 -rwxr-xr-x demo-perms/bin/greet.sh
600 -rw------- demo-perms/private/secret.env
Run the script to confirm execute works:
demo-perms/bin/greet.sh
hello from greet.sh
Note: A fourth leading digit covers special bits (setuid, setgid, sticky). You rarely need them on day one. The sticky bit on a shared directory (chmod 1777 /tmp-style) means users may delete only their own files inside — useful for drop boxes, not for ordinary project trees. Prefer plain three-digit modes until you have a clear reason for special bits.
Recursive chmod — and when not to
-R walks every file and directory under a path. That is powerful and easy to regret.
Common failure modes:
chmod -R 644 tree/strips execute from directories → you can no longer traverse them.chmod -R 755 tree/adds execute to every regular file → noisy and sometimes surprising for tools that treat “executable” as meaningful.
Prefer targeted changes. Fix one script:
chmod 755 demo-perms/bin/greet.sh
Or limit recursion to paths you listed first:
find demo-perms -type f -name '*.sh' -print
find demo-perms -type f -name '*.sh' -exec chmod 755 {} +
Directories that others should enter, without making every file executable:
find demo-perms -type d -exec chmod 755 {} +
find demo-perms -type f -exec chmod 644 {} +
chmod 600 demo-perms/private/secret.env
chmod 755 demo-perms/bin/greet.sh
Note: When you need bulk path selection with filters (age, size, prune rules), lean on find or pipe names through xargs with an echo dry-run first. Never reach for chmod -R 777 as a fix — it hides the real ownership or mode problem and opens the tree to everyone.
chown: who owns the path
Ownership is separate from mode. ls -l shows user group after the mode column; stat can print both:
stat -c '%U:%G %n' demo-perms/shared/notes.txt
Change owner and group together (usually requires root when the new owner is not you):
# illustrative — replace alice/devs with names that exist on your box
sudo chown alice:devs demo-perms/shared/notes.txt
stat -c '%U:%G %n' demo-perms/shared/notes.txt
Change only the group (colon with empty user, or chgrp):
sudo chown :devs demo-perms/shared/notes.txt
# same idea:
sudo chgrp devs demo-perms/shared/notes.txt
On many systems the file’s owner may change the group to another group they belong to, without sudo. Changing the user almost always needs elevated privileges.
Recursive ownership has the same caution as recursive mode:
# careful: affects the whole tree
sudo chown -R alice:devs demo-perms/
Verify a sample path after any recursive update:
stat -c '%U:%G %n' demo-perms/private/secret.env
Note: Wrong owner with a correct mode still produces “Permission denied” for the user who needs the file. When debugging access, check both stat ownership and the mode bits — then ask whether the process runs as the user you think it does.
umask: defaults for new files
chmod fixes what already exists. umask shapes what new files and directories receive when a process creates them.
Base permissions before umask:
- New files start from
666(no execute by default) - New directories start from
777
The umask bits are cleared from that base. Check your current mask:
umask
0022
Your shell may print 0022 or 022. Some desktop or shared-group setups use 0002 instead — new files then land as 664 rather than 644. The subtraction rule is the same either way.
With 0022 (often written 022):
- File:
666−022→644(rw-r--r--) - Directory:
777−022→755(rwxr-xr-x)
A stricter mask for private work:
umask 077
mkdir -p demo-perms/umask-demo
touch demo-perms/umask-demo/a.txt
stat -c '%a %A %n' demo-perms/umask-demo demo-perms/umask-demo/a.txt
700 drwx------ demo-perms/umask-demo
600 -rw------- demo-perms/umask-demo/a.txt
Restore a typical interactive default when you are done experimenting:
umask 022
umask in a shell affects that shell (and children) until you change it again or open a new session. To make a preference stick, set it in your shell startup file (for example ~/.bashrc). Services and daemons may use their own umask from unit files or init scripts — fixing your interactive shell does not rewrite how nginx creates logs.
Note: umask does not rewrite existing paths. Tighten a secret that already exists with chmod 600; use umask 077 so future creates in that session stay private.
Quick reference card
Keep this nearby until the flags become muscle memory:
| Goal | Command |
|---|---|
| Inspect mode + owner | ls -l path or stat -c '%a %A %U:%G %n' path |
| Add owner execute | chmod u+x file |
| Private file | chmod 600 file / chmod go-rwx file |
| Normal file / dir | chmod 644 file / chmod 755 dir |
| Executable script | chmod 755 script.sh |
| Set exact class bits | chmod u=rw,g=r,o= file |
| Owner and group | sudo chown user:group path |
| Group only | sudo chown :group path or chgrp group path |
| Recursive (careful) | chmod -R … / chown -R … — prefer find -type f/d |
| Show creation mask | umask |
| Private create defaults | umask 077 (then touch / mkdir) |
| Typical create defaults | umask 022 |
Practice drills
Use demo-perms and try these without peeking. The point is to choose symbolic vs octal, and chmod vs chown vs umask, with intent.
- Print octal mode, symbolic mode, and
user:groupfordemo-perms/shared/notes.txtin onestatcommand. - Reset
demo-perms/bin/greet.shto644, then make it owner-executable with a symbolic mode, and confirm withstat(expect744). - Set
demo-perms/private/secret.envto owner read/write only using octal600. - Using
find, set all directories underdemo-permsto755and all regular files to644, then restoresecret.envto600andgreet.shto755. - Set
umask 077, createdemo-perms/drill-private/{dir,file.txt}, and explain the modes you see; then restoreumask 022.
When you are ready to compare, here are solid answers — not the only ones, but clear and portable:
stat -c '%a %A %U:%G %n' demo-perms/shared/notes.txt
chmod 644 demo-perms/bin/greet.sh
chmod u+x demo-perms/bin/greet.sh
stat -c '%a %A %n' demo-perms/bin/greet.sh
chmod 600 demo-perms/private/secret.env
find demo-perms -type d -exec chmod 755 {} +
find demo-perms -type f -exec chmod 644 {} +
chmod 600 demo-perms/private/secret.env
chmod 755 demo-perms/bin/greet.sh
umask 077
mkdir -p demo-perms/drill-private/dir
touch demo-perms/drill-private/file.txt
stat -c '%a %A %n' demo-perms/drill-private/dir demo-perms/drill-private/file.txt
# expect 700 and 600
umask 022
Clean up when finished:
rm -rf demo-perms
If you can work through those five comfortably, you already cover most real permission work: read the mode before you change it, pick symbolic or octal for clarity, keep recursion targeted, fix ownership when the bits alone are not enough, and use umask for future creates instead of hoping every new file lands private. Start with stat or ls -l, change the smallest set of paths that solves the problem, and leave chmod -R 777 out of the toolbox.