chmod in Linux: File and Directory Permissions

chmod in Linux: File and Directory Permissions

Reading time1 min
#linux#chmod#permissions#security#sysadmin

chmod in Linux: File and Directory Permissions

Every file and directory on Linux has an owner, a group and three sets of permissions. chmod changes those permissions. It is one of the most used commands on any server, and one of the easiest to get wrong with a careless -R or 777.

Reading permissions

$ ls -l deploy.sh
-rwxr-x--- 1 deploy devops 812 Mar  3 10:15 deploy.sh

The first character is the type: - for a file, d for a directory, l for a symlink. The next nine characters are three groups of three:

  • rwx for the user who owns the file (deploy)
  • r-x for the group (devops)
  • --- for everyone else ("others")

r is read, w is write, x is execute. To see the numeric mode directly:

$ stat -c '%a %U:%G %n' deploy.sh
750 deploy:devops deploy.sh

Symbolic mode

Symbolic mode changes specific bits and leaves the rest alone. The format is who, operator, permissions:

  • who: u (owner), g (group), o (others), a (all three)
  • operator: + add, - remove, = set exactly
  • permissions: r, w, x
chmod u+x deploy.sh          # owner can execute
chmod u-w release-notes.md   # owner can no longer modify by accident
chmod g-w config.yml         # group loses write
chmod o= secrets.env         # others get nothing
chmod u=rw,go=r notes.txt    # exactly rw-r--r--
chmod a+r README.md          # everyone can read

Without a class letter, chmod +x script.sh acts like a+x, except that bits blocked by your umask are skipped. With the common umask of 022 that means chmod +x adds execute for everyone, but chmod +w only adds write for the owner.

Numeric (octal) mode

Each class is one digit: read is 4, write is 2, execute is 1, and you add them up. 7 is rwx, 6 is rw-, 5 is r-x, 4 is r--, 0 is nothing.

ModeSymbolicTypical use
644rw-r--r--regular files, configs everyone may read
640rw-r-----configs a service group must read
600rw-------SSH private keys, files with secrets
755rwxr-xr-xscripts, binaries, most directories
750rwxr-x---directories for the owner and one group
700rwx------~/.ssh, private directories
chmod 600 ~/.ssh/id_ed25519
chmod 700 ~/.ssh
chmod 640 /etc/my-app/config.yml

Numeric mode always sets all bits at once. Use it when you know the final state. Use symbolic mode when you want to change one bit and keep the rest. SSH is a good example of why this matters: it refuses to use a private key that other users can read.

Permissions on directories

On a directory the same letters mean different things:

  • r lets you list the names inside (ls).
  • w lets you create, delete and rename entries, but only together with x.
  • x lets you enter the directory and reach files inside by name (cd, opening a file whose name you know).

A few consequences that surprise people:

  • To read /srv/my-app/config.yml a user needs x on /srv and /srv/my-app, plus r on the file itself.
  • Deleting a file depends on write permission on the directory, not on the file. A read-only file in a writable directory can still be deleted.
  • chmod 711 dir lets others open files whose names they know without being able to list the directory.

namei -l shows the permissions of every component of a path. It is the fastest way to find the directory that blocks access:

namei -l /srv/my-app/config.yml

Recursive changes without breaking things

chmod -R 755 dir makes every file executable. chmod -R 644 dir removes x from the directories, and then nobody can enter them. Two correct ways:

# capital X: execute only on directories and on files that already have it
chmod -R u=rwX,go=rX /srv/my-app

# or set directories and files separately
find /srv/my-app -type d -exec chmod 755 {} +
find /srv/my-app -type f -exec chmod 644 {} +

Giving access to a specific user

chmod only knows owner, group and others. It cannot grant rights to one named user. Depending on the case, use one of these instead.

Change the owner with chown. Only root can do this:

sudo chown deploy deploy.sh
sudo chown -R www-data:www-data /var/www/my-site

Use a shared group when several users need the same access:

sudo groupadd devs
sudo usermod -aG devs deploy          # takes effect at the next login
sudo chgrp -R devs /srv/shared
sudo chmod -R g+rwX /srv/shared
sudo find /srv/shared -type d -exec chmod g+s {} +

The last line sets the setgid bit on the directories, so new files inherit the devs group instead of the creator's primary group.

Use ACLs for one-off grants. Install the acl package if the commands are missing:

setfacl -m u:deploy:rx /srv/reports
setfacl -m u:deploy:r /srv/reports/summary.csv
setfacl -d -m u:deploy:rX /srv/reports    # default for new files
getfacl /srv/reports

A + after the mode in ls -l output means the file has an ACL.

Special bits: setuid, setgid, sticky

An optional fourth digit goes in front:

  • 4 setuid: an executable runs with the owner's privileges, like /usr/bin/passwd. Linux ignores it on scripts. Do not set it on your own files.
  • 2 setgid: on a directory, new files inherit the directory's group.
  • 1 sticky: on a directory, only the file's owner (or root) can delete or rename it. /tmp is 1777.
chmod 2775 /srv/shared     # drwxrwsr-x
chmod 1777 /srv/dropbox    # drwxrwxrwt
chmod g+s /srv/shared      # setgid, symbolic form

GNU chmod keeps existing setuid and setgid bits on a directory when you give a three-digit mode such as 755. To clear them use chmod g-s dir or a five-digit mode like 00755.

Why chmod 777 is the wrong fix

777 gives everyone read, write and execute. On a script, any local user or any compromised service on the host can change what runs next time root runs it. On a directory, anyone can delete or replace files. It makes a permission error go away by hiding the real cause, which is usually the wrong owner or a missing group.

Instead:

  1. Find out who actually needs access: the web server user (www-data on Debian and Ubuntu, nginx or apache on the RHEL family), a deploy user, a group.
  2. Fix ownership with chown or a shared group.
  3. Grant the minimum: 640 for configs, 750 for directories, an ACL for a single extra user.

To find world-writable files that slipped through:

sudo find / -xdev -type f -perm -0002 2>/dev/null

On RHEL, Fedora and other SELinux systems, "Permission denied" with correct modes is often SELinux. Check the context with ls -Z and recent denials with sudo ausearch -m avc -ts recent.

Only the owner of a file or root can change its mode. chmod: changing permissions of 'file': Operation not permitted means you need sudo or a chown first.

umask: defaults for new files

New files start from 666 and new directories from 777, minus the umask. With umask 022, files get 644 and directories 755. With 002, common for regular users on Ubuntu and the RHEL family, they get 664 and 775. Check yours with umask. Set it in ~/.bashrc for your shell, or with UMask=0027 in a systemd unit for a service.

Quick reference

ls -l file                       # show permissions
stat -c '%a %n' file             # show octal mode
chmod u+x script.sh              # owner can execute
chmod 600 ~/.ssh/id_ed25519      # private key
chmod 700 ~/.ssh                 # private directory
chmod -R u=rwX,go=rX dir         # safe recursive fix
chmod 2775 shared/               # new files inherit the group
chmod 1777 dropbox/              # sticky, like /tmp
sudo chown user:group file       # fix the owner instead of using 777
setfacl -m u:deploy:rx dir       # grant one extra user
namei -l /path/to/file           # find the directory that blocks access