KB
Linux Administration

Linux File Permissions, Ownership and ACLs

6 min read1284 words11 code blocks

At a glance#

  • Purpose: Understand and correctly set Linux file permissions, ownership, special bits and POSIX ACLs.
  • Applies to: Any Linux distribution.
  • Risk: High — a careless recursive chmod or chown on a system path can render a server unbootable.
  • Time: Reference article; individual fixes take minutes.

Overview#

Most "permission denied" tickets come down to one of four things: wrong owner, wrong mode, a parent directory the user cannot traverse, or SELinux. This article covers the first three and points at the fourth.

Warning: Never run chmod -R 777 to make something work. It grants every user on the system write access, and on a web server it means any compromised site can rewrite any other site's files. It converts a small problem into an incident.

The permission model#

Every file has an owner, a group, and three permission sets:

text
-rwxr-xr--  1 root www-data  1024 Aug 11 10:30 script.sh
 │└┬┘└┬┘└┬┘    │      │
 │ │  │  │     │      └── group
 │ │  │  │     └───────── owner
 │ │  │  └── other:  r--  (read)
 │ │  └───── group:  r-x  (read, execute)
 │ └──────── owner:  rwx  (read, write, execute)
 └────────── type:   -    (regular file; d = directory, l = symlink)

Numeric values:

PermissionValueOn a fileOn a directory
r read4Read contentsList contents
w write2Modify contentsCreate/delete entries
x execute1Run as a programEnter/traverse the directory
Note: On directories, x means traverse, not execute. A user needs x on every directory in a path to reach a file inside it. This is the single most common cause of a "permission denied" that looks inexplicable — the file itself is readable, but a parent directory is not traversable.

Common modes:

ModeMeaningTypical use
644Owner read/write, others readWeb content, config files
600Owner read/write onlyPrivate keys, credentials
755Owner full, others read/executeDirectories, scripts
750Owner full, group read/execute, others noneShared team directories
700Owner only~/.ssh
440Read-only for owner and groupsudoers.d files

Changing permissions and ownership#

bash
# numeric
chmod 644 file.txt
chmod 755 /var/www/example.com

# symbolic
chmod u+x script.sh          # add execute for owner
chmod g-w file.txt           # remove write for group
chmod o= file.txt            # remove all permissions for others
chmod a+r file.txt           # add read for everyone

# ownership
chown www-data file.txt
chown www-data:www-data file.txt
chgrp developers file.txt

Recursive changes done safely#

Applying one mode to both files and directories is almost always wrong — directories need x, files usually should not have it.

bash
# directories to 755, files to 644
find /var/www/example.com -type d -exec chmod 755 {} \;
find /var/www/example.com -type f -exec chmod 644 {} \;

# ownership recursively
chown -R www-data:www-data /var/www/example.com
Warning: Check the path twice before any recursive command. chown -R on / or /etc will break the system, and there is no undo. A useful habit is to run the find without -exec first to see what it matches.

Special permission bits#

setuid and setgid#

bash
# setgid on a directory - new files inherit the directory's group
chmod g+s /srv/shared
chmod 2775 /srv/shared

# check
ls -ld /srv/shared
# drwxrwsr-x  <- the 's' in the group position

setgid on a directory is the standard way to make a shared team folder work: everything created inside it belongs to the team group automatically, regardless of who created it.

Warning: setuid on an executable makes it run as the file's owner rather than the caller. A setuid-root binary is a privilege escalation route if it has any flaw. Audit for unexpected ones: ``bash sudo find / -perm -4000 -type f 2>/dev/null ``

Sticky bit#

bash
chmod +t /tmp
chmod 1777 /tmp

With the sticky bit set, users can only delete files they own, even in a world-writable directory. This is why /tmp is safe.

Default permissions with umask#

umask subtracts from the default (666 for files, 777 for directories):

bash
umask                 # show current, usually 022
umask 027             # files 640, directories 750

Make it permanent for a user in ~/.bashrc, or system-wide in /etc/profile.

umaskNew filesNew directories
022644755
027640750
077600700

POSIX ACLs#

Standard permissions handle one owner and one group. ACLs handle "this group needs read, that user needs write, everyone else nothing".

Check filesystem support#

bash
mount | grep " / "

ACLs are enabled by default on ext4 and xfs on modern distributions. If not, add acl to the mount options in /etc/fstab.

Viewing and setting#

bash
# view
getfacl /srv/shared/report.xlsx

# grant a user
setfacl -m u:jsmith:rw /srv/shared/report.xlsx

# grant a group
setfacl -m g:developers:rx /srv/shared

# recursive
setfacl -R -m g:developers:rx /srv/shared

# default ACL - inherited by new files in this directory
setfacl -d -m g:developers:rw /srv/shared

# remove one entry
setfacl -x u:jsmith /srv/shared/report.xlsx

# remove all ACLs
setfacl -b /srv/shared/report.xlsx
Note: A + at the end of the permission string in ls -l means ACLs are present: ``text -rw-rw----+ 1 root developers 1024 Aug 11 10:30 report.xlsx ` ls -l alone will not show you the real access rights when that + is there — use getfacl`.

The mask#

bash
setfacl -m m::rx /srv/shared

The ACL mask caps the maximum permission any named user or group can receive. If an ACL entry appears correct but access is still denied, check the mask — getfacl marks affected entries with #effective:.

Diagnosing "permission denied"#

Work through in this order:

bash
# 1. What are the file's permissions and owner?
ls -l /path/to/file

# 2. Are there ACLs?
getfacl /path/to/file

# 3. Can the user traverse every parent directory?
namei -l /path/to/file

# 4. What groups is the user actually in?
id username

# 5. Test as that user
sudo -u username cat /path/to/file

# 6. Is SELinux blocking it?
getenforce
sudo ausearch -m avc -ts recent

namei -l is the fastest way to find a non-traversable parent directory — it prints the permissions of every component in the path.

Note: If permissions look correct and access is still denied on a RHEL-family system, it is very often SELinux rather than permissions. Check the context with ls -Z and look for AVC denials before changing any mode.

Verification#

bash
# confirm the mode and owner
ls -l /path/to/file
stat /path/to/file

# confirm ACLs
getfacl /path/to/file

# confirm the intended user can actually read or write
sudo -u www-data test -r /var/www/example.com/index.html && echo "readable"
sudo -u www-data test -w /var/www/example.com/uploads && echo "writable"

Testing as the target user is the only real proof. Reading the mode and assuming is how permission bugs survive.

Troubleshooting#

SymptomCause and fix
Permission denied but the mode looks rightA parent directory lacks x. Run namei -l.
Web server returns 403Web user cannot traverse or read. Check with sudo -u www-data.
User in the correct group still deniedGroup membership needs a fresh login. Run id to confirm what is actually active.
Uploads fail but reads workDirectory needs w and x for the writing user.
ACL set but ignoredThe ACL mask is restricting it. Check getfacl for #effective:.
Files created with the wrong groupSet setgid on the parent directory.
SSH refuses the key~/.ssh must be 700 and authorized_keys 600, owned by the user.
Everything breaks after a recursive chmodRestore from backup. On RHEL, rpm --setperms -a repairs package-owned files.