Linux File Permissions, Ownership and ACLs
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
chmodorchownon 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:
-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:
| Permission | Value | On a file | On a directory |
|---|---|---|---|
r read | 4 | Read contents | List contents |
w write | 2 | Modify contents | Create/delete entries |
x execute | 1 | Run as a program | Enter/traverse the directory |
Note: On directories,xmeans traverse, not execute. A user needsxon 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:
| Mode | Meaning | Typical use |
|---|---|---|
644 | Owner read/write, others read | Web content, config files |
600 | Owner read/write only | Private keys, credentials |
755 | Owner full, others read/execute | Directories, scripts |
750 | Owner full, group read/execute, others none | Shared team directories |
700 | Owner only | ~/.ssh |
440 | Read-only for owner and group | sudoers.d files |
Changing permissions and ownership#
# 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.txtRecursive changes done safely#
Applying one mode to both files and directories is almost always wrong — directories need x, files usually should not have it.
# 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.comWarning: Check the path twice before any recursive command.chown -Ron/or/etcwill break the system, and there is no undo. A useful habit is to run thefindwithout-execfirst to see what it matches.
Special permission bits#
setuid and setgid#
# 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 positionsetgid 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:setuidon 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#
chmod +t /tmp
chmod 1777 /tmpWith 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):
umask # show current, usually 022
umask 027 # files 640, directories 750Make it permanent for a user in ~/.bashrc, or system-wide in /etc/profile.
| umask | New files | New directories |
|---|---|---|
022 | 644 | 755 |
027 | 640 | 750 |
077 | 600 | 700 |
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#
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#
# 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.xlsxNote: A+at the end of the permission string inls -lmeans ACLs are present: ``text -rw-rw----+ 1 root developers 1024 Aug 11 10:30 report.xlsx`ls -lalone will not show you the real access rights when that+is there — usegetfacl`.
The mask#
setfacl -m m::rx /srv/sharedThe 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:
# 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 recentnamei -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#
# 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#
| Symptom | Cause and fix |
|---|---|
| Permission denied but the mode looks right | A parent directory lacks x. Run namei -l. |
| Web server returns 403 | Web user cannot traverse or read. Check with sudo -u www-data. |
| User in the correct group still denied | Group membership needs a fresh login. Run id to confirm what is actually active. |
| Uploads fail but reads work | Directory needs w and x for the writing user. |
| ACL set but ignored | The ACL mask is restricting it. Check getfacl for #effective:. |
| Files created with the wrong group | Set 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 chmod | Restore from backup. On RHEL, rpm --setperms -a repairs package-owned files. |