Linux File Permissions, Explained Properly
Permissions are the most misunderstood corner of Linux — and the source of half of all server problems, from "403 Forbidden" to SSH refusing your key. This guide builds the model from scratch. Ten minutes here saves hours of cargo-cult chmod 777 later.
Reading the permission string
Run ls -l and look at the first column of any line:
-rwxr-xr-- 1 diego developers 4096 Aug 4 09:41 deploy.sh That ten-character string is four pieces. The first character is the type: - for a regular file, d for a directory, l for a symlink. The remaining nine characters are three triplets of rwx — read, write, execute — for three audiences in order: the owner (user), the group, and others (everyone else). A dash means that permission is absent.
So -rwxr-xr-- reads: regular file; owner can read, write and execute; group members can read and execute; everyone else can only read. The third and fourth columns of ls -l (diego developers) tell you who the owner and group actually are.
What r, w and x really mean
For files, the letters mean what you'd expect: read the contents, modify the contents, run it as a program. For directories they mean something subtly different, and this is where most confusion lives:
ron a directory: list the names inside it (lsworks).won a directory: create, delete and rename entries inside it — even files you don't own.xon a directory: enter it and access things inside (cdworks, paths through it resolve).
Two consequences worth engraving: a directory without x is a locked door regardless of the permissions of what's inside, and deleting a file is governed by the directory's write bit, not the file's.
The numbers: where 755 comes from
Each permission has a value: r = 4, w = 2, x = 1. Add them per triplet and you get one digit per audience:
| Digits | Triplets | Meaning | Typical use |
|---|---|---|---|
755 | rwx r-x r-x | Owner: everything. Rest: read + execute | Directories, scripts, binaries |
644 | rw- r-- r-- | Owner edits, everyone reads | Regular files, web content |
700 | rwx --- --- | Owner only | Private directories, ~/.ssh |
600 | rw- --- --- | Owner reads/writes, nobody else anything | SSH keys, credentials, .env files |
777 | rwx rwx rwx | Everyone can do everything | Almost never the right answer |
Why the pattern "755 for directories, 644 for files"? Directories need x to be enterable; regular files should not be executable unless they are actually programs. Web servers follow exactly this convention.
chmod: changing permissions
Numeric mode sets all nine bits at once; symbolic mode adjusts specific ones:
chmod 644 index.html # set exactly rw-r--r--
chmod +x deploy.sh # add execute for everyone
chmod u+x deploy.sh # add execute for the owner only
chmod g-w,o-r report.txt # remove group write and others read
chmod -R 755 public_html/ # recursive — see the warning below chmod -R 755 on mixed content: it makes every file executable too. The clean recipe uses find: find . -type d -exec chmod 755 + for directories and find . -type f -exec chmod 644 + for files.chown: changing who owns things
Permissions are meaningless without knowing who the "owner" and "group" are. chown assigns them:
sudo chown www-data:www-data /var/www/html -R # web root belongs to the web server
sudo chown diego:developers shared-report.txt The pattern behind most "my app can't write its own files" incidents: a deployment ran as your user, so new files belong to you, while the service runs as www-data. Re-owning the tree fixes it. See the full chown guide.
The recipes you'll actually need
- SSH refuses your key:
chmod 700 ~/.ssh && chmod 600 ~/.ssh/id_ed25519. SSH deliberately rejects keys that others can read. - "Permission denied" running a script:
chmod +x script.sh, or run it via the interpreter:bash script.sh. - Web server 403: files 644, directories 755, and every directory in the path down from
/needsxfor the server's user. - Shared team directory:
chown :team dir && chmod 2775 dir— the leading 2 (setgid) makes new files inherit the directory's group.
Practice it now
This sandbox has a backup.sh that isn't executable. Check it with ls -l, fix it with chmod 755 backup.sh, and verify the string changed:
Keep going
The chmod reference and chown reference cover every option, and the chmod lessons of the interactive course make you do this from muscle memory.