The Permission String
Run ls -l and every line starts with a 10-character string:
-rwxr-xr-- 1 alice devs 4096 Apr 28 09:12 script.sh
Each character has a fixed meaning:
File type character
-regular fileddirectorylsymbolic linkccharacter devicebblock devicepnamed pipessocket
Permission bits
Each of the three groups (owner, group, other) has three bits:
| Char | Name | On a file | On a directory |
|---|---|---|---|
| r | read | open & read content | list entries (ls) |
| w | write | modify content | create/delete/rename entries |
| x | execute | run as program | enter directory (cd) |
| - | (none) | permission not granted | |
Octal Notation
Each group of three bits maps to a single octal digit (0–7). Read=4, Write=2, Execute=1. Add them together.
| Octal | Binary | Symbolic | Meaning |
|---|---|---|---|
| 7 | 111 | rwx | full access |
| 6 | 110 | rw- | read & write |
| 5 | 101 | r-x | read & execute |
| 4 | 100 | r-- | read only |
| 3 | 011 | -wx | write & execute |
| 2 | 010 | -w- | write only |
| 1 | 001 | --x | execute only |
| 0 | 000 | --- | no access |
A full permission set is three octal digits: owner · group · other.
755 → rwxr-xr-x # typical executable / directory
644 → rw-r--r-- # typical regular file
600 → rw------- # private file (SSH keys, etc.)
700 → rwx------ # private directory
chmod
chmod changes permissions. Two syntaxes exist: octal and symbolic.
Octal syntax
chmod 644 file.txt
chmod 755 /usr/local/bin/myscript
chmod -R 750 /var/myapp # recursive
Symbolic syntax
Format: [who][op][perms]
- who:
u(owner),g(group),o(other),a(all) - op:
+add,-remove,=set exactly - perms:
r,w,x
chmod u+x script.sh # add execute for owner
chmod go-w file.txt # remove write from group and other
chmod a=r readonly.txt # set everyone to read-only
chmod u=rwx,g=rx,o= dir/ # owner full, group r+x, other nothing
chown & chgrp
Every file has an owner (a user) and an owning group.
chown alice file.txt # change owner
chown alice:devs file.txt # change owner and group
chown :devs file.txt # change group only (chgrp equivalent)
chown -R www-data:www-data /var/www/html # recursive
umask
umask sets the default permissions mask applied when new files and directories are created. It works by subtracting bits from the maximum defaults (666 for files, 777 for directories).
umask # show current mask
umask 022 # set mask
With umask 022:
Files: 666 - 022 = 644 (rw-r--r--)
Directories: 777 - 022 = 755 (rwxr-xr-x)
With umask 077 (restrictive — common for home dirs):
Files: 666 - 077 = 600 (rw-------)
Directories: 777 - 077 = 700 (rwx------)
Set permanently in ~/.bashrc, ~/.profile, or /etc/profile.
Special Permission Bits
A fourth octal digit sits in front of the three standard digits, controlling three special bits.
| Bit | Octal | On file | On directory |
|---|---|---|---|
| setuid (SUID) | 4000 | runs as file owner, not caller | (no standard effect) |
| setgid (SGID) | 2000 | runs as owning group | new files inherit directory's group |
| sticky bit | 1000 | (legacy, mostly ignored) | only owner/root can delete entries |
In ls output
-rwsr-xr-x # SUID set — 's' replaces 'x' in owner field
-rwxr-sr-x # SGID set — 's' replaces 'x' in group field
drwxrwxt # sticky — 't' replaces 'x' in other field
-rwSr--r-- # capital S: bit set but execute NOT set
Setting special bits
chmod 4755 /usr/bin/mysetuid # SUID + 755
chmod 2775 /shared/project # SGID + 775
chmod 1777 /tmp # sticky + 777
# Symbolic equivalents:
chmod u+s file
chmod g+s dir/
chmod +t /tmp
find / -perm /6000 -type f 2>/dev/null. Never set SUID on shell scripts — Linux ignores it, but the intent itself is a red flag.
Access Control Lists (ACLs)
Standard Unix permissions allow exactly one owner and one group. ACLs extend this to arbitrary users and groups without changing ownership.
# View ACL
getfacl file.txt
# Grant bob read+write
setfacl -m u:bob:rw file.txt
# Grant group contractors read-only
setfacl -m g:contractors:r file.txt
# Remove a specific ACL entry
setfacl -x u:bob file.txt
# Remove all ACL entries
setfacl -b file.txt
When an ACL is present, ls -l shows a + after the permission string: -rw-r--r--+. The effective mask (mask:: in getfacl output) acts as an upper bound on ACL permissions — it does not affect the owning user.
How Permission Checks Work
The kernel checks in order and stops at the first match:
- Is the process UID == file owner UID? → apply owner bits.
- Is the process GID (or any supplementary GID) == file group? → apply group bits.
- Neither → apply other bits.
Only one set of bits applies. If you own a file but the owner bits deny access, the group or other bits are not consulted — even if they would grant it.
x bit set somewhere on a file to execute it.
Quick Reference
# Common permission sets
chmod 600 ~/.ssh/id_rsa # private key: owner read+write only
chmod 644 ~/.ssh/id_rsa.pub # public key: world-readable
chmod 700 ~/.ssh # .ssh directory: owner only
chmod 755 /var/www/html # web root: served by www-data
chmod 640 /etc/app/config.env # app config: owner rw, group r
# Audit / inspection
stat file.txt # full file metadata incl. permissions
find . -perm 777 # find world-writable files
find . -perm /o+w -type f # same with bitwise match
find / -perm /4000 -type f # find SUID files