▶Prefer to watch? This short video runs this page's main commands on a real Ubuntu 24.04 machine, with on-screen captions and no voiceover. Click to play, or watch it on YouTube.
ls -l | chmod | chown | umask
chmod 644 file.txt
Every file and directory on Linux has an OWNER, a GROUP and a set of PERMISSIONS that say who may read, write or run it. "Permission denied" is one of the most common
messages you will ever see, and understanding why is one of the most valuable skills a Linux user or admin can have. This sheet covers reading permissions, changing them
(chmod, chown, chgrp), the default mask (umask), the special bits (setuid, setgid, sticky), ACLs, file attributes, groups and sudo, plus recipes for shared folders, web
servers and SSH keys, security habits, and a checklist for "Permission denied". Try the commands on a scratch folder first: mkdir ~/perm-lab && cd ~/perm-lab
Reading ls -l
ls -l # long listing ls -la include hidden files ls -ld dir the directory itself, not its contents
-rw-r--r-- 1 alice staff 1024 Sep 26 10:15 notes.txt
drwxr-xr-x 2 alice staff 4096 Sep 26 10:15 projects
lrwxrwxrwx 1 alice staff 11 Sep 26 10:15 link -> notes.txt
Column 1 Type and permissions (10 characters) Column 2 Number of hard links Column 3 Owner (user) Column 4 Group Column 5 Size in bytes then date and name
The first character is the FILE TYPE: - regular file d directory l symbolic link c character device b block device s socket p named pipe
The next nine characters are three groups of three: USER (owner), GROUP, OTHERS. Each group shows r (read), w (write), x (execute), or - (not allowed):
- rw- r-- r-- = regular file; the owner can read and write, the group can read, everyone else can read
d rwx r-x r-x = directory; the owner has full access, the group and others can list and enter it
- rwx --- --- = the owner alone can read, write and run it
A trailing + after the permissions (rw-r--r--+) means the file has an ACL. A trailing . means an SELinux label. A t or s in the last spot is a special bit (see below).
Which set applies to you? Linux checks in order: are you the OWNER? Then only the owner permissions apply. Else are you in the file's GROUP? Then the group permissions apply. Otherwise the OTHERS permissions apply.
It does not add them together: an owner with --- on the user set is denied even if others have r--. (root bypasses most of these checks.)
stat file # full detail: mode in both forms, owner, group, size, times, inode
stat -c '%a %A %U:%G %n' * # octal, symbolic, owner:group and name for every file (great for scripts and audits)
ls -ln # show numeric user and group IDs (useful when names do not resolve)
id # who am I: user ID, group ID, and all my groups id alice for another user
groups # my groups groups alice
What r, w and x mean (files and directories differ!)
On a FILE On a DIRECTORY
r read View the contents (cat, less) List the names inside (ls). You still cannot open the files without x
w write Change or truncate the contents Create, rename or DELETE entries inside it (needs x too). Deleting a file depends on the DIRECTORY, not on the file's own permissions
x execute Run it as a program or script ENTER the directory (cd) and reach things inside it by name. This is called "search" permission
Consequences worth memorizing:
- To open /home/alice/docs/a.txt you need x on EVERY directory along the path (/, /home, /home/alice, /home/alice/docs) plus r on the file. One locked parent blocks everything below it
- A directory with r but no x: you can list names, but cannot read the files or see sizes. A directory with x but no r: you can open a file if you know its exact name, but cannot list the directory
- To delete a file you need w and x on its DIRECTORY (unless the sticky bit is set: see below). The file's own permissions do not matter
- To run a script directly (./script.sh) it needs x. To run it as bash script.sh it only needs r
- Read-only for you does not mean read-only for others. Check the group and others sets too
The numbers: octal permissions
Each permission has a value: r = 4 w = 2 x = 1 none = 0. Add them up per set (user, group, others):
7 = rwx (4+2+1) 6 = rw- (4+2) 5 = r-x (4+1) 4 = r-- (4) 3 = -wx (2+1) 2 = -w- (2) 1 = --x (1) 0 = --- (nothing)
So 644 = rw-r--r-- 755 = rwxr-xr-x 600 = rw------- 700 = rwx------ 664 = rw-rw-r-- 775 = rwxrwxr-x 750 = rwxr-x--- 640 = rw-r----- 777 = rwxrwxrwx (everyone can do everything: avoid)
A fourth digit in front adds the special bits: 4 = setuid 2 = setgid 1 = sticky. So 2775 = setgid + rwxrwxr-x, 1777 = sticky + rwxrwxrwx (like /tmp), 4755 = setuid + rwxr-xr-x
Common modes and what to use them for:
644 Ordinary files (documents, HTML, most configs) 755 Programs, scripts and directories that others must enter
600 Private files (SSH private keys, secrets, tokens) 700 Private directories (~/.ssh, a personal scripts folder)
640 Files the group may read (app config with passwords, logs) 750 Directories the group may enter but others may not
664 Files a team edits together 775 Directories a team shares (usually with setgid)
440 Read-only for owner and group (sudoers files) 1777 Public writable directories where users only delete their own files (/tmp)
chmod: change permissions
Numeric (octal) mode: set everything at once
chmod 644 file.txt # rw-r--r--
chmod 755 script.sh # rwxr-xr-x
chmod 600 ~/.ssh/id_ed25519 # private key: only you
chmod 700 ~/.ssh # private folder
chmod 2775 shared/ # setgid + rwxrwxr-x
Symbolic mode: change only what you name. who: u (user/owner) g (group) o (others) a (all) action: + (add) - (remove) = (set exactly) what: r w x X s t
chmod u+x script.sh # give the owner execute
chmod g+w file.txt # let the group write
chmod o-rwx secret.txt # take everything away from others
chmod a+r readme.txt # everyone may read (a is "all") chmod +x script.sh means a+x (subject to umask)
chmod u=rw,g=r,o= file.txt # exactly rw-r----- (an empty value after = clears that set)
chmod g=u file.txt # make the group permissions equal to the owner's
chmod u+s program # setuid chmod g+s dir setgid chmod +t dir sticky
chmod --reference=good.txt other.txt # copy the permissions of another file
Recursive: chmod -R changes a whole tree. The dangerous part: files and directories usually need DIFFERENT modes. Use capital X:
chmod -R u=rwX,g=rX,o= project/ # X = execute only for directories (and for files that are already executable)
find project/ -type d -exec chmod 755 {} + # directories: rwxr-xr-x (the usual clean-up: directories 755, files 644)
find project/ -type f -exec chmod 644 {} + # files: rw-r--r--
find project/ -type f -name '*.sh' -exec chmod 755 {} + # then make scripts executable
Check first, change second: ls -l or stat before, and chmod -c (report each change) or -v (verbose) while doing it. chmod -R 777 is almost never the right answer (see the security section).
Only the owner (or root) can chmod a file. Errors: "Operation not permitted" = you are not the owner. "Read-only file system" = the disk is mounted read-only.
chown and chgrp: change owner and group
sudo chown alice file.txt # change the owner (only root can give a file away)
sudo chown alice:staff file.txt # owner AND group sudo chown :staff file.txt group only sudo chown alice: file.txt owner and the owner's login group
sudo chown -R alice:staff project/ # recursively (double-check the path first!)
chgrp staff file.txt # change the group. You can do this if you own the file and belong to that group
sudo chown -R www-data:www-data /var/www/site # typical web server ownership
sudo chown --reference=good.txt other.txt # copy owner and group from another file
sudo chown -h alice link # change a symlink itself (default: it follows the link) chown -R does not follow symlinks by default (safer)
Never run sudo chown -R user / or on a system directory like /etc, /usr or /var. It breaks the system, and it is a classic beginner disaster. Print the path you are about to use with echo first.
Find files owned by nobody or a removed account: find / -xdev \( -nouser -o -nogroup \) 2>/dev/null
umask: the default permissions of new files
New files start at 666 (rw-rw-rw-) and new directories at 777, and then the umask REMOVES bits from those. Files never get execute by default.
umask # show it (usually 0022 on many systems, 0002 on some with user private groups)
umask 022 # new files 644, new directories 755 (only the owner can write)
umask 002 # new files 664, new directories 775 (owner and group can write. Good for teams)
umask 027 # new files 640, new directories 750 (others get nothing. A good server default)
umask 077 # new files 600, new directories 700 (private)
umask -S # symbolic form: u=rwx,g=rx,o=rx
How to calculate: file = 666 minus umask, directory = 777 minus umask (bit by bit, so 022 removes write from group and others).
Where to set it: a shell session (type it), ~/.bashrc or ~/.profile (one user), /etc/login.defs or /etc/profile (system default), a systemd unit (UMask=0027), cron/scripts (start them with umask).
It affects only files created AFTER you set it. Existing files keep their modes.
Special permission bits
setuid (4000) On an executable: it runs with the OWNER's identity instead of yours. Example: /usr/bin/passwd is owned by root and setuid, so ordinary users can change their password. ls shows s in the owner x spot (rwsr-xr-x)
setgid (2000) On an executable: it runs with the file's GROUP. On a DIRECTORY: new files and folders inside inherit the directory's GROUP (perfect for shared team folders). ls shows s in the group spot (rwxrwsr-x)
sticky (1000) On a DIRECTORY: users can delete or rename only the files THEY own (or if they own the directory), even if others have write access. Example: /tmp (drwxrwxrwt). ls shows t at the end
chmod u+s program chmod 4755 program # setuid
chmod g+s /srv/team chmod 2775 /srv/team # setgid on a directory
chmod +t /srv/drop chmod 1777 /srv/drop # sticky
Capital S or T in ls output (rwSr--r--, rwxrwxrwT) means the special bit is set but the matching execute bit is NOT. Usually a mistake.
Security notes: setuid programs run as another user, so bugs in them can give attackers root. Keep the list short and audit it. Linux ignores setuid on shell scripts (and modern systems ignore it on interpreted scripts for good reason).
Find them all: find / -xdev -perm -4000 -type f 2>/dev/null setgid files: find / -xdev -perm -2000 -type f 2>/dev/null (compare with a saved list to spot new ones)
Users and groups (who gets which permissions)
id # my identity and groups getent passwd alice account details getent group staff a group and its members
groups alice # the groups of a user
sudo useradd -m -s /bin/bash bob # create a user with a home directory and bash sudo passwd bob set the password
sudo groupadd developers # create a group
sudo usermod -aG developers bob # ADD bob to a group (-a is essential: without it bob is REMOVED from all other supplementary groups)
sudo gpasswd -d bob developers # remove bob from a group sudo usermod -g developers bob change bob's PRIMARY group
newgrp developers # start a shell with a different active group (new files get that group)
sudo groupdel developers # delete a group (files owned by it keep the numeric ID)
Group changes take effect at the NEXT login. After usermod -aG , the user must log out and back in (or run newgrp GROUPNAME or su - bob ) before the new group applies. Check with id .
Primary group: the group new files get by default (see the setgid trick to override for a directory). Supplementary groups: extra groups that grant access. Ubuntu and similar systems give each user a private group with the same name.
/etc/passwd (users, world-readable), /etc/shadow (password hashes, root-only), /etc/group (groups), /etc/gshadow. Do not edit them by hand: use vipw, vigr, usermod and friends.
Users below 1000 (or 500 on older systems) are usually system accounts (daemons) with no login shell (/usr/sbin/nologin). Services should run as their own unprivileged user, never as root.
sudo and su: running things with more power
sudo command # run one command as root (asks for YOUR password) sudo -u alice command run as another user sudo -i a root login shell sudo -l what am I allowed to do?
su - alice # switch to another user (asks for THEIR password, or root's) su - become root (needs the root password, which is often disabled on Ubuntu)
sudo !! # re-run the last command with sudo (bash)
sudo -k # forget my cached password now
sudo visudo # edit /etc/sudoers safely (it checks the syntax before saving). NEVER edit sudoers with a normal editor
sudo visudo -f /etc/sudoers.d/webteam # a separate file per team is tidier. Files there must have mode 0440
Examples for sudoers files:
alice ALL=(ALL:ALL) ALL # alice may run anything as anyone
%admin ALL=(ALL:ALL) ALL # every member of group "admin"
%webteam ALL=(root) /usr/bin/systemctl restart nginx, /usr/bin/systemctl reload nginx # only these commands
deploy ALL=(root) NOPASSWD: /usr/local/bin/deploy.sh # no password prompt for one command (be careful)
Prefer giving people sudo for specific commands over full root. Avoid wildcards in sudo command arguments: they can be abused. Sudo logs to /var/log/auth.log (Debian) or /var/log/secure (RHEL).
Redirects run as YOU, not root: sudo echo x > /etc/file fails. Use echo x | sudo tee /etc/file (or tee -a to append), or sudo sh -c 'echo x > /etc/file'.
Shared folders that just work (teams)
Goal: everyone in the group "developers" can create, read and edit each other's files in /srv/projects.
sudo groupadd developers ; sudo usermod -aG developers alice ; sudo usermod -aG developers bob
sudo mkdir -p /srv/projects
sudo chown root:developers /srv/projects
sudo chmod 2775 /srv/projects # setgid: new files inherit the group "developers". 775: group can write
For new files to be group-WRITABLE, people need umask 002 (rw-rw-r--). Set it in their shell, or use a default ACL instead (works regardless of umask):
sudo setfacl -R -m g:developers:rwX /srv/projects
sudo setfacl -R -d -m g:developers:rwX /srv/projects # -d = default ACL: inherited by everything created later
Add the sticky bit if members should not delete each other's files: chmod 3775 /srv/projects (setgid + sticky).
Test as a user: sudo -u bob touch /srv/projects/test ; ls -l /srv/projects ; getfacl /srv/projects
Web server files (example)
Typical safe layout: files owned by a deploy user, readable by the web server, writable by nobody except during deploys.
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 {} +
Give the web server write access ONLY to the folders that need it (uploads, cache): sudo chown -R www-data:www-data /var/www/site/uploads ; sudo chmod 770 /var/www/site/uploads
The web server user is www-data (Debian/Ubuntu), nginx or apache (RHEL family). Never make code directories writable by the web server, and never use 777 to "fix" an upload error.
On SELinux systems also check labels (ls -Z) and booleans, not just the mode bits.
SSH files (permissions matter a lot)
SSH refuses keys and configs that others can write, and prints "WARNING: UNPROTECTED PRIVATE KEY FILE!" or silently falls back to a password.
chmod 700 ~/.ssh # the folder
chmod 600 ~/.ssh/id_ed25519 ~/.ssh/id_rsa # private keys: MUST be owner-only
chmod 644 ~/.ssh/id_ed25519.pub # public keys can be readable
chmod 600 ~/.ssh/authorized_keys ~/.ssh/config # 644 is acceptable for authorized_keys too, but never group or world WRITABLE
chmod go-w ~ # your home directory must not be group or world writable, or sshd (StrictModes yes) rejects your keys
chown -R $USER:$USER ~/.ssh # ownership must be you
On the server, if key login fails, check /var/log/auth.log (or journalctl -u ssh) for "Authentication refused: bad ownership or modes for directory".
Important system files (typical modes)
/etc/passwd 644 root:root /etc/shadow 640 root:shadow (RHEL: 000 root:root) /etc/group 644 /etc/sudoers 440 root:root
/etc/ssh/sshd_config 600 or 644 /etc/ssh/ssh_host_*_key 600 root:root /root 700 /tmp 1777 root:root /var/tmp 1777
/var/log/*.log 640 (root:adm or syslog:adm) /home/USER 750 or 700 (many systems default to 755: tighten if privacy matters) /etc/cron.d/* 644 root:root
Check the current state before "fixing": dpkg --verify (Debian) rpm -V PACKAGE / rpm -Va (RHEL) list files whose modes differ from the package. On RHEL: rpm --setperms PACKAGE restores them.
ACLs: permissions for more than one owner and one group
Standard permissions allow one user and one group. ACLs (Access Control Lists) let you name any number of users and groups.
getfacl file # show the ACL
setfacl -m u:alice:rw file # give user alice read and write -m g:devs:rx a group
setfacl -m u:alice:rwx,g:devs:rx dir/ # several entries at once
setfacl -x u:alice file # remove one entry setfacl -b file remove ALL ACL entries
setfacl -R -m g:devs:rX project/ # recursive
setfacl -d -m g:devs:rwX dir/ # DEFAULT ACL on a directory: applied to new files and folders inside it
setfacl -m m::rwx file # set the MASK: the maximum any named user or group entry can get (chmod g+... changes the mask!)
getfacl -R --absolute-names dir > acl-backup.txt ; setfacl --restore=acl-backup.txt # back up and restore ACLs
ls -l shows a + at the end when an ACL exists. The "mask" limits the effective permissions: getfacl shows "#effective:r--" next to entries reduced by it.
Copying: cp -a and rsync -aA keep ACLs; tar needs --acls. Most Linux filesystems (ext4, XFS, btrfs) support ACLs with default mount options.
File attributes (beyond mode bits)
lsattr file # list attributes
sudo chattr +i file # IMMUTABLE: nobody, not even root, can change, delete or rename it until you remove the flag
sudo chattr -i file # remove it
sudo chattr +a logfile # APPEND-only: can add to the file but not change earlier data (good for logs)
sudo chattr -R +i /etc/critical # recursive
An "Operation not permitted" error for root usually means an immutable flag or a similar restriction: check lsattr . Attributes work on ext4, XFS and others (support varies by filesystem).
Capabilities: giving a program one special power instead of full root
getcap /usr/bin/ping # show capabilities
getcap -r / 2>/dev/null # every file with capabilities (a good security audit)
sudo setcap 'cap_net_bind_service=+ep' /usr/local/bin/myserver # let a program bind to ports below 1024 without being root
sudo setcap -r /usr/local/bin/myserver # remove them
Capabilities beat setuid root for many tasks, because the program gets only the one power it needs. Learn the names with: man capabilities
SELinux and AppArmor (when the mode bits look fine but access is still denied)
Mandatory access control adds a second gate. Standard permissions AND the security policy must both allow the action.
ls -Z file # show the SELinux label ps -eZ | grep nginx a process's label id -Z my label
getenforce # Enforcing, Permissive or Disabled sudo setenforce 0 permissive until reboot (for testing ONLY)
sudo restorecon -Rv /var/www/site # reset labels to the policy default (fixes many "denied after copying files" cases)
sudo semanage fcontext -a -t httpd_sys_content_t "/srv/site(/.*)?" ; sudo restorecon -Rv /srv/site # label a NON-default location
sudo ausearch -m avc -ts recent # recent denials sudo sealert -a /var/log/audit/audit.log plain-English advice (setroubleshoot)
sudo aa-status # AppArmor (Ubuntu/Debian/SUSE): loaded profiles sudo journalctl -k | grep -i apparmor denials
Do not turn SELinux or AppArmor off to "fix" a problem. Find the denial, and add the right label, boolean or profile rule.
Mount options that override permissions
mount | grep ' /data ' # options in effect findmnt /data
ro mounted read-only (writes fail with "Read-only file system") noexec programs cannot be run from there nosuid setuid/setgid bits are ignored nodev device files are ignored
Common hardening: /tmp, /var/tmp and /dev/shm mounted with noexec,nosuid,nodev . USB and network shares are often mounted with noexec. Change a live mount: sudo mount -o remount,rw /data
FAT, exFAT and NTFS have no Unix permissions: the mount options decide who owns everything: mount -o uid=1000,gid=1000,umask=022 ... (or fmask= and dmask= )
NFS: the server's rules and the numeric user IDs matter. IDs must match on client and server. "root_squash" makes remote root act as nobody. Samba: see the Samba sheet.
Finding permission problems
namei -l /path/to/file # shows the permissions of EVERY directory along the path: the fastest way to find the blocked step
namei -mo /var/www/html/index.html # compact form with mode and owner
ls -ld /path /path/to /path/to/file # check each level by hand
sudo -u www-data test -r /var/www/site/index.html && echo readable || echo BLOCKED # test access AS another user
sudo -u www-data cat /var/www/site/index.html # try it as that user
getfacl /path/to/file # ACL that might be involved
lsattr /path/to/file # immutable flag?
df -h /path ; df -i /path ; mount | grep /path # full disk, out of inodes, or read-only mount?
journalctl -xe | tail ; sudo dmesg | tail # kernel and audit messages
strace -f -e trace=file -o trace.txt command # find which exact file path fails with EACCES (see the strace sheet): grep EACCES trace.txt
Audit searches (run as root):
find / -xdev -type f -perm -0002 2>/dev/null # world-writable files
find / -xdev -type d -perm -0002 ! -perm -1000 2>/dev/null # world-writable directories WITHOUT the sticky bit (a risk)
find / -xdev -perm -4000 -type f 2>/dev/null # setuid programs find / -xdev -perm -2000 -type f setgid programs
find /home -maxdepth 2 -type d -perm -0002 # writable-by-everyone home content
find /etc -type f -perm -0002 # someone made a config world-writable
find / -xdev \( -nouser -o -nogroup \) 2>/dev/null # files with no valid owner or group (leftover from deleted accounts)
find /var/www -type f ! -perm 640 # files that differ from your standard
find . -newer reference.txt -ls # recently changed files (who touched what)
Copying, moving and archiving with permissions
cp file dest # the copy is owned by YOU, with mode = source mode minus your umask
cp -p file dest # preserve mode, owner (if root), and timestamps cp -a src dst archive: recursive and preserves everything, including ACLs and links
mv # a move within one filesystem keeps owner and mode. Across filesystems it behaves like copy and delete
rsync -a src/ dst/ # -a preserves permissions, owner, group, times and symlinks rsync -aAX also ACLs and extended attributes
tar czf backup.tgz dir/ # tar stores modes and owners. Extracting as root restores owners; as a normal user it does not. tar xzpf backup.tgz -p keep permissions tar --acls --xattrs keep ACLs
install -m 755 -o root -g root script /usr/local/bin/script # copy and set mode and ownership in one step (scripts and packaging love this)
install -d -m 750 -o app -g app /var/lib/app # create a directory with a mode and owner
Security habits
- Least privilege: give each user, service and file only the access it needs
- Avoid chmod 777 and chmod -R 777 . It lets ANY user or process change the files. If something "only works with 777", the real problem is a wrong owner, group, ACL or SELinux label. Find that instead
- Services run as their own unprivileged users, not root. Give the service user ownership of only what it writes to
- Keep secrets (keys, tokens, .env files) at 600 or 640 with a tight group, and out of web roots and git repos
- Set a safer default umask (027 for servers, 077 for personal secrets) and check what your services create
- Audit setuid, setgid, world-writable files and capabilities regularly, and investigate anything new (see the audit searches)
- Do not put users in the sudo or admin group casually. Use narrow sudoers rules, and review them
- Keep home directories private if others share the machine: chmod 750 or 700 ~
- World-writable directories must have the sticky bit (1777), like /tmp
- Do not run scripts or programs as root just to get past a permission error. Fix the permission
- Be very careful with recursive commands (chmod -R, chown -R, rm -R). Print the target path first, and never point them at /, /etc, /usr, /var or your whole home unintentionally
- Prefer groups and ACLs over sharing a single account or opening things to "others"
- Log and monitor: watch /var/log/auth.log or /var/log/secure for sudo use and failures, and consider auditd rules for sensitive files
Common problems
"Permission denied" Run the checklist below. Start with namei -l PATH and id
"Operation not permitted" You are not the owner or root, or the file is immutable (lsattr), or a capability, SELinux or AppArmor rule blocks it. root can hit this too
"Read-only file system" The mount is read-only (ro), or the filesystem hit an error and remounted itself read-only. Check mount, dmesg, and repair carefully
"Permission denied" even though the file is 777 A parent directory blocks you (missing x on some level), the mount is noexec or ro, SELinux/AppArmor, an ACL mask, or you are running as a user the file does not allow. Check the whole path
"bash: ./script.sh: Permission denied" The script is not executable (chmod +x), or the filesystem is mounted noexec, or the shebang points to something not executable
"bash: ./script.sh: /bin/bash^M: bad interpreter" Windows line endings. Fix with dos2unix, not permissions
"cannot remove ...: Permission denied" on a file you own Deleting depends on the DIRECTORY: you need w and x on the parent directory (and sticky bit rules apply)
"cannot create regular file ...: Permission denied" No w or x on the target directory, or a read-only mount, or a full disk/quota
"sudo: ... is not in the sudoers file. This incident will be reported" The user is not allowed to use sudo. An admin must add them to the sudo/wheel/admin group or sudoers file
Group membership does not work after usermod -aG The user must log out and in again (or run newgrp / su - user). Check with id
"UNPROTECTED PRIVATE KEY FILE" / SSH ignores my key chmod 600 the private key, chmod 700 ~/.ssh, and make sure your home directory is not group or world writable
"chmod: changing permissions of 'x': Operation not permitted" You do not own the file. Use sudo, or ask the owner
"chown: changing ownership of 'x': Operation not permitted" Only root can give files to another user
New files get the wrong group Use the setgid bit on the directory (chmod g+s), or a default ACL, or newgrp
New files are not group-writable umask is 022. Use umask 002, or a default ACL (setfacl -d -m g:GROUP:rwX dir)
Permissions reset after a package upgrade or reboot A package, tmpfiles (systemd-tmpfiles), a cron job, a configuration tool (Ansible/Puppet) or a service is changing them. Look in /usr/lib/tmpfiles.d, /etc/tmpfiles.d and your config management
NTFS/USB drive shows everything 777 or cannot be chmod'ed These filesystems have no Unix permissions. The mount options set them. Use ext4 for Linux-only data
NFS files show numbers instead of names, or wrong owner User IDs differ between client and server. Align UIDs and GIDs, or use idmapping (NFSv4) or a directory service
Docker or a container writes files as root into my folder Run the container with --user $(id -u):$(id -g), or fix ownership after with chown. Bind mounts keep the host's numeric IDs
Everything is fine but the web server still cannot read it SELinux label (ls -Z, restorecon), the parent directories, or the web user is not in the right group
The "Permission denied" checklist (work down the list)
1. Who am I, and who is the process? id ; ps -o user,group,cmd -p PID (a service user is not YOU)
2. Can I traverse the path? namei -l /full/path ; every directory needs x for that user
3. Are the file mode, owner and group right? ls -l file ; stat file
4. Is there an ACL that changes the result? getfacl file (look at the mask and named entries)
5. Am I in the group (in THIS session)? id (log out and in after usermod)
6. Is the filesystem read-only or noexec? findmnt -T file ; mount | grep ...
7. Is the disk or quota full? df -h ; df -i ; quota -v
8. Is the file immutable? lsattr file
9. Does SELinux or AppArmor deny it? ls -Z ; ausearch -m avc -ts recent ; aa-status ; journalctl -k | grep -i denied
10. Am I really running the command I think? type command ; which command ; strace -f -e trace=file command (shows the exact failing path with EACCES)
11. Is it the network side (NFS root_squash, Samba rules, container user)? check the server and container configuration
Quick reference
r=4 w=2 x=1 chmod 644 file (rw-r--r--) chmod 755 dir or script (rwxr-xr-x) chmod 600 secret (rw-------)
chmod u+x file chmod g+w file chmod o-rwx file chmod -R u=rwX,g=rX,o= dir
chown user:group file chown -R user:group dir chgrp group file sudo usermod -aG group user (then log out and in)
umask 022 (files 644, dirs 755) umask 002 (664/775) umask 027 (640/750) umask 077 (600/700)
chmod 4755 (setuid) chmod 2775 (setgid dir) chmod 1777 (sticky dir, like /tmp)
getfacl file setfacl -m u:alice:rw file setfacl -d -m g:devs:rwX dir setfacl -b file
lsattr / chattr +i file getcap / setcap namei -l /path/to/file stat -c '%a %U:%G %n' file
find / -xdev -perm -4000 -type f find / -xdev -type f -perm -0002 find / -xdev \( -nouser -o -nogroup \)
Tips
Read permissions as three groups of three (owner, group, others), and remember r=4, w=2, x=1. 644 for files, 755 for directories and programs, 600 for secrets.
On a directory, x means "enter", r means "list", and w means "create and delete entries". To reach a file you need x on every folder above it.
Use namei -l PATH first when you see "Permission denied". It shows exactly which folder in the path is blocking you.
Do not use chmod 777 to fix problems. Find the real cause: the owner, the group, a parent directory, an ACL, a mount option or an SELinux label.
After usermod -aG, log out and back in. New groups apply only to new sessions, and id shows what is active.
Use the setgid bit (2775) or a default ACL for team folders so new files keep the right group. Use the sticky bit (1777) on shared drop folders.
Use capital X in recursive changes (chmod -R u=rwX,g=rX,o=), or use find with -type d and -type f, so files do not become executable by accident.
Be careful with chown -R and chmod -R. Print the target first, and never point them at /, /etc, /usr or /var.
Keep secrets at 600 or 640, SSH keys at 600, ~/.ssh at 700, and your home directory not group- or world-writable.
Audit setuid files, world-writable files and capabilities now and then with find and getcap, and investigate anything unexpected.