How the numbers are built
A Unix file mode is twelve bits. The bottom nine split into three groups of three: owner, group, and others. Within each group the bits are read (4), write (2), and execute (1), and they simply add up. Read plus write is 6. Read plus execute is 5. All three is 7. That is the entire arithmetic — chmod 754 means owner 7 (rwx), group 5 (r-x), others 4 (r--).
The three bits above those nine are the special bits, and they are what the optional fourth digit carries: setuid (4000), setgid (2000), and the sticky bit (1000). chmod 755 and chmod 0755 are identical because the leading digit defaults to zero. When you see a four-digit mode like 2775, the leading 2 is setgid, not part of the permission arithmetic.
| Octal | Symbolic | Read | Write | Execute |
|---|---|---|---|---|
| 0 | --- | no | no | no |
| 1 | --x | no | no | yes |
| 2 | -w- | no | yes | no |
| 4 | r-- | yes | no | no |
| 5 | r-x | yes | no | yes |
| 6 | rw- | yes | yes | no |
| 7 | rwx | yes | yes | yes |
Reading ls -l output
ls -l prints ten characters. The first is the file type — - for a regular file, d for a directory, l for a symlink, c/b for device nodes. The remaining nine are the owner, group, and other triads. So drwxr-x--- is a directory the owner can do anything with, the group can enter and list, and everyone else cannot touch at all: mode 750.
Read the full explanation of chmod calculator results ↓Show less ↑
The special bits hijack the execute position of their triad, which is why they are easy to misread. A lowercase s in the owner's execute slot means setuid and execute are both set. An uppercase S means setuid is set but execute is not — nearly always a mistake. The same rule applies to s/S in the group slot for setgid, and t/T in the others slot for the sticky bit. drwxrwxrwt on /tmp is mode 1777: world-writable, but sticky, so users cannot delete each other's files.
What execute means on a directory
This trips up nearly everyone. On a directory, the three bits do not mean what they mean on a file:
- Read (`r`) lets you list the names inside. Without it,
lsfails — but you can still open a file inside if you know its exact name and have execute. - Write (`w`) lets you create, delete, and rename entries. Note that deleting a file depends on the *directory* being writable, not the file. This is why you can delete a read-only file you do not own from a directory you do own.
- Execute (`x`) — often called the *search* bit — lets you traverse into the directory and stat things inside it. Without it, nothing below the directory is reachable at all, whatever the permissions down there say.
The practical consequence: a directory almost always needs its execute bit set wherever it has read. chmod -R 644 across a tree is the classic self-inflicted outage — it strips x from every directory and the whole path becomes unreachable, producing 403s from the web server for files that are perfectly readable. Use find to set files and directories separately.
# Directories to 755, files to 644 — never a blanket chmod -R
find /var/www/html -type d -exec chmod 755 {} +
find /var/www/html -type f -exec chmod 644 {} +
# Symbolic form does the same thing in one pass:
# capital X sets execute ONLY on directories and files that already have it
chmod -R u=rwX,go=rX /var/www/html
# See what you have before you change it
stat -c '%a %A %U:%G %n' /var/www/html/index.php # Linux
stat -f '%Lp %Sp %Su:%Sg %N' /var/www/html/index.php # macOS/BSDWhy 777 is almost always the wrong fix
chmod 777 grants read, write, and execute to every account on the machine. On a shared host that includes every other customer. On a VPS it includes every service account — the mail daemon, the monitoring agent, and anything an attacker manages to run as nobody. A world-writable PHP file is a persistent backdoor waiting for anyone who can already run code as any user at all; they rewrite your file, and the web server executes it as itself.
It is also almost never the actual fix. "Permission denied" from a web application means the *web server user* cannot read or write the path. The correct remedy is to make that user the owner, or put it in the owning group, and keep the mode restrictive. chown -R www-data:www-data uploads && chmod 755 uploads solves the upload-directory problem properly; chmod 777 uploads solves it by removing the lock from the door.
chmod -R 777 on a web root, treat the site as potentially compromised: anything with write access could have modified it in the window. Restore files from source control rather than just tightening the mode.Modes worth memorising
| Mode | Symbolic | Use for |
|---|---|---|
644 | rw-r--r-- | Static web files, PHP source, config that is not secret |
755 | rwxr-xr-x | Directories, shell scripts, binaries |
640 | rw-r----- | Files the web server group must read but the world must not |
600 | rw------- | .env, wp-config.php, API tokens, ~/.ssh/id_ed25519 |
700 | rwx------ | ~/.ssh, private deploy directories |
444 | r--r--r-- | Files you want to make accidental-edit-proof |
1777 | rwxrwxrwt | Shared scratch directories like /tmp — sticky, so only owners delete |
2775 | rwxrwsr-x | Group-collaborative directories; new files inherit the group |
SSH is the strictest consumer of permissions you are likely to meet. sshd refuses to use a private key that any group or other can read, and refuses ~/.ssh/authorized_keys if the home directory itself is group-writable — silently falling back to password auth, which then fails. 700 on ~/.ssh, 600 on private keys, 644 on authorized_keys and public keys.
umask: why new files are never 777
Processes create files requesting mode 666 and directories requesting 777; the umask then subtracts bits. The common default 022 clears write for group and other, producing 644 files and 755 directories. A umask of 002 produces 664 and 775, which is what you want on a machine where a group collaborates. umask masks bits off — it can never add a bit that the creating program did not ask for, which is why new files are never executable by default.
umask # -> 0022
umask -S # -> u=rwx,g=rx,o=rx
# 666 & ~022 = 644 for files; 777 & ~022 = 755 for directories
install -m 600 /dev/null secret.env # bypass umask explicitlySymbolic chmod, when it is the better tool
Octal sets every bit at once, which is exactly what you want for a known target state and exactly what you do not want for a targeted change. Symbolic mode edits bits relative to what is there: chmod +x deploy.sh adds execute for everyone the umask allows, chmod u+x,go-w file adds owner-execute and removes group/other write in one call, and chmod g=u file copies the owner triad onto the group. The capital X mentioned above only adds execute where it makes sense, which makes chmod -R u=rwX,go=rX safe on a mixed tree where a blanket octal recursion would not be.
One thing chmod cannot do is change *who* the owner and group are — that is chown and chgrp. If a mode looks right and access still fails, check ownership next, then check every parent directory's execute bit, then check whether SELinux or AppArmor is denying it (ausearch -m avc on RHEL-family systems will tell you in seconds what ls -l never will).