🔐 Linux Permissions Quick Reference

Linux permissions cheat sheet / survival guide

Video: Linux permissions (Command Line Tips video)
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.