In this guide
Your deployment script fails with "Permission denied." A tutorial says to run chmod -R 777 and it works — so you ship it. Months later your project directory is world-writable, your SSH key permissions are broken, and nginx throws a 403 you cannot explain. Every one of these has the same root cause: you never learned what the nine permission characters in ls -l actually mean.
This guide explains Linux file permissions the way they should have been taught: the three classes, the nine bits, symbolic vs octal notation, ownership, the special bits, and the exact commands that fix the five permission problems students hit most — without ever reaching for 777.
Reading the nine characters
Run ls -l on any Linux system and you see something like this:
-rw-r--r-- 1 ved students 2048 Oct 10 09:12 app.py
Ignore the leading - (file type: - for file, d for directory, l for link). The next nine characters are three groups of three:
- Characters 2-4: what the owner (user
ved) can do:rw-= read + write, no execute. - Characters 5-7: what the group (
students) can do:r--= read only. - Characters 8-10: what everyone else can do:
r--= read only.
Each position is one permission:
- r (read): read the file's contents. For a directory: list the filenames inside it.
- w (write): modify the file. For a directory: create, delete, or rename files inside it — this is the subtle one, covered below.
- x (execute): run the file as a program. For a directory: enter it and access files inside by name.
The directory case trips up everyone: to open /var/www/site/index.html you need execute permission on every directory in the path (/, /var, /var/www, /var/www/site), even if the file itself is world-readable. nginx 403 errors are usually a missing +x on some parent directory, not a problem with the file.
And write-on-a-directory is its own privilege: deleting a file requires write permission on the directory, not on the file. A read-only file in a writable directory can still be deleted by the directory's owner.
Octal notation: the three numbers
Each group of three bits is one octal digit (r=4, w=2, x=1; add them):
| Symbol | Octal | Meaning |
|---|---|---|
rwx |
7 | Full control |
rw- |
6 | Read + write |
r-x |
5 | Read + execute |
r-- |
4 | Read only |
--- |
0 | Nothing |
So 644 = rw-r--r-- (owner reads/writes, everyone else reads) and 755 = rwxr-xr-x (owner full, everyone else read + execute). The two defaults you should memorise:
- Files: 644 — you edit them; the world reads them.
- Directories: 755 — you control them; the world can enter and list.
When you create files, the system applies a umask that strips bits from the maximum (666 for files, 777 for directories). The common umask 022 strips write for group and others: 666 - 022 = 644, 777 - 022 = 755. That is where the defaults come from.
Symbolic notation: changing one thing
Octal sets all nine bits at once; symbolic mode changes one class's one permission without touching the rest:
chmod u+x deploy.sh # give the owner execute permission
chmod g-w secrets.txt # take write away from the group
chmod o= app.key # others get nothing at all
chmod a+r README.md # everyone can read (a = all)
Use symbolic notation when you know exactly which bit is wrong; use octal when you are setting a known-good standard (644, 755, 600).
Ownership: chown and chgrp
Every file has an owner and a group. Two commands change them:
chown ved app.py # change the owner
chown ved:students app.py # change owner and group together
chgrp students shared/ # change just the group
Only root can give away ownership (changing chown as a normal user is restricted for security reasons — otherwise you could create files owned by someone else and bypass quotas). Use sudo chown when fixing ownership of deployed files.
The group exists so a set of users can share access: put your project collaborators and the web server user in one group, set the directory's group accordingly, and you never need to open permissions to the world.
The special bits: setuid, setgid, sticky
Three extra bits appear in the execute position as s, S, t, or T:
- setuid (4): the file runs with the owner's privileges, not the runner's. This is how
passwdlets you change your own password (it needs root to edit the password database). A security red flag on anything you did not install yourself. - setgid (2): on a file, runs with the group's privileges. On a directory, new files inside inherit the directory's group instead of the creator's primary group — the useful one for shared project folders.
- sticky bit (1): on a directory (classically
/tmp), anyone can create files but only the file's owner can delete them. That is why/tmpshowsdrwxrwxrwt.
chmod 4755 script.sh # setuid + 755
chmod 2755 shared/ # setgid on directory: new files inherit the group
chmod 1755 uploads/ # sticky: users can't delete each other's files
In octal these sit in a fourth leading digit (4/2/1). You will rarely set them yourself; you must be able to read them when auditing a system.
The 777 trap: why it "works" and why it is wrong
chmod -R 777 makes everything readable, writable, and executable by everyone. It fixes every permission error instantly — and creates three real problems:
- Security: any process, any other user on the machine, any compromised script can now modify your code, read your secrets, or plant executables. On a shared VPS or lab machine this is not theoretical.
- SSH breaks: OpenSSH refuses to use keys that are group- or world-writable.
chmod 777 ~/.ssh/id_rsadoes not make SSH more permissive — it makes SSH stop working entirely, with an unhelpful "bad permissions" error. - Executables everywhere: every file, including your data files and logs, becomes "executable," which confuses tools and scanners and masks real problems.
The right fix for a permission error is always the same three questions: which file, which user, which bit? Then change exactly that bit.
The five permission problems students actually hit
1. "Permission denied" running a script
./deploy.sh # Permission denied
chmod u+x deploy.sh
./deploy.sh # works
Files created by editors and downloads are not executable by default. Granting execute to the owner is the whole fix.
2. nginx / Apache 403 Forbidden
The web server runs as its own user (often www-data) and needs read on the file plus execute on every parent directory:
sudo chown -R ved:www-data /var/www/site
sudo chmod -R 755 /var/www/site
sudo chmod -R 644 /var/www/site/*.html
If 403 persists after this, check a parent directory (namei -l /var/www/site/index.html shows the permission of every path component — the fastest way to find the culprit).
3. SSH "bad permissions" / key ignored
SSH demands strict private-key permissions:
chmod 700 ~/.ssh
chmod 600 ~/.ssh/id_rsa
chmod 644 ~/.ssh/id_rsa.pub ~/.ssh/authorized_keys
This is the number-one fix for "Permission denied (publickey)" on a correctly configured server. The SSH keys guide covers the key side of this problem.
4. Cannot write to an uploaded/shared folder
For a folder two users (you and the web server) must both write to, use a shared group plus setgid:
sudo chgrp -R webteam uploads/
sudo chmod -R 2775 uploads/ # group-writable, new files inherit the group
5. Git shows every file as modified after a Windows/Linux switch
Git tracks the executable bit. Files checked out on Windows gain +x and every file shows as changed. Fix per-repo:
git config core.fileMode false
A 60-second permission audit
When something is broken and you do not know where, run this sequence:
ls -l <file>— read the nine characters. Who owns it? Which class is your user in (owner, group, other)?idandgroups— confirm which user and groups you actually are right now (sudo changes this).namei -l <path>— check every directory in the path for a missingx.ps aux | grep nginx(or your service) — confirm which user the failing process runs as; fix permissions for that user, not for yourself.- Change the minimum: one bit, one class. Re-test before changing anything else.
Common mistakes
- Recursive chmod on the wrong directory.
chmod -R 644 /var/wwwstrips execute from every directory, making them all un-enterable. If you must recurse, do directories and files separately:find . -type d -exec chmod 755 {} +andfind . -type f -exec chmod 644 {} +. - Running everything as root via sudo instead of fixing ownership — it works, then one typo deletes the wrong tree with full privileges.
- World-writable config files holding API keys or database passwords (
-rw-rw-rw-on.envis a leak waiting to happen; use 600). - Confusing read on a directory with access.
r--on a directory lets you list names but not open anything inside withoutx. - Forgetting the group exists. Jumping to "others" permissions (or 777) when a shared group plus 770/775 was the correct, tighter answer.
- Ignoring umask on new files. Uploads and scripts keep arriving with wrong permissions because the creating process's umask was never set right — fix the umask, not each file.
Frequently asked questions
What is the difference between 644 and 755?
644 (rw-r--r--) is for files: owner can edit, everyone reads. 755 (rwxr-xr-x) is for directories and scripts: adds the execute bit so others can enter directories or run the script. Never 755 a data file, never 644 a directory you need to enter.
Why does SSH care about my key file permissions?
A private key readable by others is a compromised key. OpenSSH checks this and refuses to use a key that is group- or world-accessible (it must be 600). It is a safety rail, not a bug.
What does the t in drwxrwxrwt mean?
The sticky bit on /tmp: anyone can create files there, but only the file's owner (or root) can delete or rename them. Without it, any user could delete anyone else's temporary files.
How do I see permissions of every folder in a path at once?
namei -l /full/path/to/file prints each path component with its permissions in one view — the fastest way to find which parent directory is blocking access.
Is sudo chmod -R 777 /project ever acceptable?
On a disposable local container you will throw away in an hour: harmless. Anywhere else — a VPS, a lab machine, anything with real data or a network port: no. Learn the three questions (which file, which user, which bit) instead.
Limitations
- This guide covers classic Unix discretionary permissions. Mandatory access control (SELinux, AppArmor) sits above these and can deny access even with perfect 755/644 — if permissions look right but access still fails on a hardened distro, check SELinux contexts (
ls -Z). - Access Control Lists (
setfacl/getfacl) allow finer per-user rules than the nine bits; they are out of scope here but worth learning next. - Permission models differ on network filesystems (NFS, SMB) and on Windows/WSL mounts — bits may be emulated or ignored there.
Where to go from here
- SSH Keys Explained: How Public-Key Login Actually Works — the key-auth side of server access, including the permission traps that break logins.
- Nginx Reverse Proxy: Host Multiple Apps on One VPS — where 403s and
www-dataownership problems actually show up. - How to Deploy a MERN App on a VPS — a full deployment walkthrough where every permission in this guide gets exercised.
- Docker for Student Projects: A Beginner's Guide — containers sidestep many permission fights by isolating the runtime — and introduce a few new ones.