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:

  1. User (owner) — if the process runs as the file’s owner.
  2. Group — if the process’s user is a member of the file’s group.
  3. 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 w on the directory, not on the file.
  • Reaching /var/www/site/index.html requires x on /, /var, /var/www and /var/www/site, plus r on 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/file shows 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; an i flag blocks all changes, even for root (chattr -i removes it).
  • Mount options — noexec prevents running programs from that filesystem; ro makes it read-only.
  • systemd sandboxing — ProtectHome=, ReadOnlyPaths= in a service unit hide or lock paths for that service.

Debugging “Permission denied”

  1. Which user is the process running as?
  2. namei -l /path/to/file — any directory missing x? File missing r/w?
  3. Is the user in the group they need? (id username; remember new groups apply at next login or service restart.)
  4. SELinux/AppArmor denials in the logs?
  5. 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.

  • Linux
  • Permissions
  • Security