Linux File Permissions and umask Explained
Half the "Permission denied" questions I get aren't mysterious once you read ls -l properly. The other half come from files created with the wrong default permissions because nobody set the umask for the service or the deploy user. This is the explanation I wish someone had given me early on: the permission bits, the special bits, what umask really does, and how to keep a shared directory writable for a team.
Reading the permission string
ls -l /var/www/example.com/html/index.php
-rw-r--r--. 1 deploy www-data 2048 Nov 8 10:12 index.php
The first character is the type (- file, d directory, l symlink). The next nine are three groups of rwx: owner (deploy), group (www-data), and everyone else. Here the owner can read and write, the group can read, others can read. The trailing . on CentOS indicates an SELinux context is present; a + means an ACL is set.
For directories, the meaning shifts: r lets you list names, x lets you enter and access files whose names you know, and w lets you create or delete entries. A directory with rw- but no x is useless; --x alone is a valid way to let a service reach files without browsing.
chmod
I use the symbolic form most of the time because it changes only what I say:
chmod u+x deploy.sh # owner can execute
chmod g+w shared/ # group can write
chmod o-rwx secrets.conf # remove everything from others
chmod -R g+rX /var/www/site # X: execute only on directories and already-executable files
Octal form sets everything at once. Each digit is r=4, w=2, x=1 summed:
chmod 644 index.php # rw-r--r--
chmod 755 bin/run.sh # rwxr-xr-x
chmod 600 ~/.ssh/id_rsa # rw-------
chmod 750 /srv/app # rwxr-x---
The capital X is the one trick worth memorising. chmod -R 755 makes every file executable, which is wrong; chmod -R u=rwX,go=rX gives directories 755 and files 644 in one pass. I got that wrong on a whole web root once.
The special bits
A fourth leading octal digit sets three special bits.
setuid (4) on an executable runs it as the file owner. /usr/bin/passwd is -rwsr-xr-x so an ordinary user can update /etc/shadow. Almost never set this yourself.
setgid (2) on a directory makes new files inherit the directory's group instead of the creator's primary group. This is the key to shared directories:
chgrp developers /srv/shared
chmod 2775 /srv/shared
ls -ld /srv/shared
drwxrwsr-x. 2 root developers 6 Nov 8 10:20 /srv/shared
sticky (1) on a directory means only a file's owner (or root) can delete it, even if the directory is world-writable. /tmp is drwxrwxrwt for this reason:
chmod 1777 /var/app/uploads
umask
umask doesn't set permissions, it removes them. A process creating a file asks for 666 (files) or 777 (directories) and the kernel clears any bit set in the umask. With the usual 022:
files: 666 - 022 = 644 (rw-r--r--)
directories: 777 - 022 = 755 (rwxr-xr-x)
With 027 the group keeps read and others get nothing: 640 and 750. With 002 the group gets write: 664 and 775. Subtraction is a mental shortcut; strictly it's a bitwise AND with the complement, which only matters for odd masks.
umask # show current, e.g. 0022
umask -S # symbolic: u=rwx,g=rx,o=rx
umask 027 # for this shell only
Setting it for users and services
For interactive users the default comes from /etc/profile and /etc/bashrc (CentOS) or /etc/login.defs via pam_umask (Ubuntu, UMASK 022). Override per user in ~/.bashrc, or system-wide with a file in /etc/profile.d/:
echo 'umask 027' | sudo tee /etc/profile.d/umask.sh
Services don't read shell profiles, which is the part people forget. For a systemd unit, use the [Service] section:
[Service]
UMask=0027
For a team sharing /srv/shared, combine setgid on the directory with a 002 umask for the members, so everything created there is group-writable and group-owned by developers.
Trying it out
umask
touch /tmp/t1 && ls -l /tmp/t1
mkdir /tmp/d1 && ls -ld /tmp/d1
sudo -u deploy sh -c 'umask; touch /srv/shared/probe' && ls -l /srv/shared/probe
The last line shows both the inherited group (setgid) and the deploy user's effective umask in a non-interactive shell, which is what cron and deploy scripts really get.
Common "Permission denied" causes
bash: ./script.sh: Permission deniedon a file that's clearly there: no execute bit (chmod +x), or the filesystem is mountednoexec(mount | grep noexec).Permission deniedon a directory you own withrw-: noxon it, or on a parent.namei -l /path/to/fileprints every component's permissions. This is my first command for these tickets.- Files created by a web app belong to
apache(orwww-data) and the deploy user can't edit them: the upload directory lacks setgid and the service umask is022. Set2775on the directory andUMask=0002in the unit, and put both users in one group. chmod: changing permissions of 'x': Operation not permitted: you're not the owner or root, or the file is immutable (lsattr x;chattr -i x).- Everything looks right in
ls -lbut access is still denied on CentOS: SELinux, not the mode bits.ls -Zandausearch -m avc -ts recent.