Linux decides who may read, change or run each file with a simple model: every file has an owner, a group, and a set of permission bits. Once you understand the model, “Permission denied” errors become easy to diagnose.
The three classes
For every file, the system checks the requesting process against three classes, in order, and uses the first that matches:
- User (owner) — if the process runs as the file’s owner.
- Group — if the process’s user is a member of the file’s group.
- Others — everyone else.
Only one class applies. If you’re the owner and the owner has no read permission, you can’t read the file — even if “others” can.
root bypasses permission checks for reading and writing (execute still requires at least one x bit).
The three permissions
| On a file | On a directory | |
|---|---|---|
r read (4) |
Read contents | List entries (ls) |
w write (2) |
Modify contents | Create, delete, rename entries |
x execute (1) |
Run as a program | Enter it (cd) and access entries by name |
Two consequences that surprise people:
- Deleting a file needs
won the directory, not on the file. - Reaching
/var/www/site/index.htmlrequiresxon/,/var,/var/wwwand/var/www/site, plusron the file.
Reading ls -l
-rw-r----- 1 deploy www-data 1200 Nov 4 10:00 .env
drwxr-xr-x 5 deploy www-data 4096 Nov 4 10:00 public
-rw-r----- → type - (file), owner rw-, group r--, others ---. The owner deploy can read and write, the group www-data (the web server) can read, nobody else can access it.
Octal notation
Each class is a digit, summing r=4, w=2, x=1:
| Mode | Meaning | Typical use |
|---|---|---|
644 |
rw-r–r– | Regular files |
640 |
rw-r—– | Config files read by a group |
600 |
rw—–– | Secrets, SSH private keys |
755 |
rwxr-xr-x | Directories, scripts |
750 |
rwxr-x— | Directories private to owner + group |
700 |
rwx—— | Private directories (~/.ssh) |
777 |
rwxrwxrwx | Almost never appropriate |
Change modes with chmod, owners with chown.
Processes, not people
Permissions apply to processes, which run as some user. Nginx workers run as www-data (or nginx), PHP-FPM pools as their configured user, MySQL as mysql. When a website can’t read or write a file, ask: which user does that process run as, and what do the owner/group/mode allow that user?
ps -o user,cmd -C nginx
ps -o user,cmd -C php-fpm8.3
Default permissions: umask
New files start from 666 (files) or 777 (directories), minus the umask. With the common umask 022, files become 644 and directories 755; with 027, 640 and 750. See umask explained.
Special bits
| Bit | On files | On directories |
|---|---|---|
| setuid (4xxx) | Runs as the file’s owner (e.g. passwd) |
— |
| setgid (2xxx) | Runs with the file’s group | New files inherit the directory’s group |
| sticky (1xxx) | — | Only owners can delete their files (/tmp) |
setgid directories are the clean solution for shared team folders. See setuid, setgid and sticky bit.
ACLs: when owner/group/others isn’t enough
Need to give a second group or one extra user access without changing ownership? Access Control Lists add per-user and per-group entries:
setfacl -m u:alice:rx /var/log/app
getfacl /var/log/app
A + at the end of the mode in ls -l (drwxr-x---+) means ACLs are present. See ACLs with setfacl.
Other layers that can deny access
If the mode looks right but access is still denied, check:
- Every parent directory —
namei -l /full/path/to/fileshows permissions along the path. - SELinux (RHEL family) or AppArmor (Ubuntu) — mandatory access control on top of permissions.
ls -Z,ausearch -m avc,journalctl | grep apparmor. - Immutable attribute —
lsattr file; aniflag blocks all changes, even for root (chattr -iremoves it). - Mount options —
noexecprevents running programs from that filesystem;romakes it read-only. - systemd sandboxing —
ProtectHome=,ReadOnlyPaths=in a service unit hide or lock paths for that service.
Debugging “Permission denied”
- Which user is the process running as?
namei -l /path/to/file— any directory missingx? File missingr/w?- Is the user in the group they need? (
id username; remember new groups apply at next login or service restart.) - SELinux/AppArmor denials in the logs?
- ACLs, immutable flags, mount options?
Sensible defaults for a web server
sudo chown -R deploy:www-data /var/www/site
sudo find /var/www/site -type d -exec chmod 750 {} +
sudo find /var/www/site -type f -exec chmod 640 {} +
sudo chmod -R g+w /var/www/site/storage /var/www/site/uploads
The deploy user owns the code, the web server can read it and write only where needed. More in WordPress file permissions.