Almost every Linux administrator has hit the "Permission denied" error at some point. We try to run a script, open a file, enter a directory, or connect via SSH, and the terminal returns the block message. The most common reaction is to throw a chmod 777 on top and move on, which fixes the symptom while opening security holes in the system.
In this guide we will show how to diagnose the actual cause of the error, identify which layer is blocking access, and apply the minimal correction needed. We cover the POSIX model, the proper use of chmod and chown, common scenarios like SSH keys and web directories, plus additional layers such as SELinux and extended attributes.
For practical exercises, a disposable server is a good idea. A VPS Linux on Serverspace works well for testing the commands in an isolated environment. The examples work the same way on Ubuntu, Debian, AlmaLinux, and Rocky.
What the "Permission denied" error means
The message appears when the Linux kernel refuses an operation because the current user lacks sufficient rights over the file or directory involved. The text is almost always the same, while the causes vary considerably.
The most frequent contexts include trying to execute a script without the execute bit, reading a file owned by another user, writing to system directories such as /etc or /var/www, accessing a folder via cd without the x bit, and SSH authentication with keys whose permissions are too open.
Before applying any fix, it helps to know that Linux checks access in three layers. The first is classic POSIX permissions (rwx for owner, group, and others). The second is extended ACLs, which allow finer rules for specific users and groups. The third is mandatory security modules: SELinux on RHEL and derivatives, AppArmor on Ubuntu and Debian.
Terminology: how Linux organizes permissions
Access control is based on three subject categories and three operation types. The categories are: file owner (user, u), file group (group, g), and other system users (others, o). Each file has a single owner and a single associated group, set when the file is created.
The operation types are read (r), write (w), and execute (x). For files, the meaning is direct. For directories there is an important subtlety: read lists the contents, write lets us create and remove files inside, and execute lets us traverse the directory, meaning cd into it and reach files by full path.
The output of ls -l shows this model in action:
-rwxr-xr-- 1 maria devs 2048 Mar 10 14:20 deploy.shThe first character indicates the type (- for regular file, d for directory, l for symbolic link). The next nine characters form three triplets, one per category: rwx for the owner, r-x for the group, r-- for others.
There are two ways to set permissions with chmod. The symbolic form uses letters and operators (chmod u+x script.sh adds execution for the owner). The octal form sums the values 4 (r), 2 (w), and 1 (x) per category, producing three digits. The most common combinations in daily work:
| Octal | Symbolic | Typical use | What it grants |
|---|---|---|---|
| 644 | rw-r--r-- | Text files, configs | Owner edits, others read |
| 755 | rwxr-xr-x | Directories, public scripts | Everyone can navigate and execute |
| 700 | rwx------ | ~/.ssh, private data | Owner-only access |
| 600 | rw------- | SSH private keys, .env files | Owner reads and writes only |
| 775 | rwxrwxr-x | Shared upload folder | Group writes, others read |
Memorizing these five combinations covers most practical situations. Anything else can be built from them with small variations.
How the permission system works
When a process tries to access a file, the kernel runs through a sequence of checks. First it compares the process UID with the file owner's UID. If they match, owner bits apply. Otherwise, it checks whether any of the user's groups matches the file's group and applies group bits. If neither condition holds, the others triplet applies.
This flow has important implications. For deletion, the write permission on the parent directory matters more than the permission on the file itself. That is why we can sometimes remove read-only files without obstacles, as long as we have write access on the containing directory.
Another implication involves the execute bit on directories. If /home/maria/projects has no x bit for the current user, no file inside can be accessed, even with open permissions. The entire path to the file must be traversable.
Finally, it is worth knowing about umask. When we create a new file, the system starts from 666 (files) or 777 (directories) and subtracts the umask. The default of 022 results in 644 for files and 755 for directories.
Practical scenarios where the error appears
Shell script without execute permission
We create a script, write a few lines, and try to run it:
$ ./deploy.sh
bash: ./deploy.sh: Permission denied
The cause is that the file was created without the x bit. The fix is to add execution for the owner:
chmod u+x deploy.sh
./deploy.sh
If the script should run for all users, we can use chmod 755. For personal use, chmod 700 keeps the script private.
SSH key with permissions too open
When connecting to a server:
Permissions 0644 for '~/.ssh/id_rsa' are too open.
It is required that your private key files are NOT accessible by others.
This behavior comes from the StrictModes yes parameter, enabled by default in sshd_config. The server refuses any key that can be read by other users on the client machine. The fix requires closed permissions on the key and the directory:
chmod 700 ~/.ssh
chmod 600 ~/.ssh/id_rsa
chmod 644 ~/.ssh/id_rsa.pub
chmod 600 ~/.ssh/authorized_keys
Writing to a system directory
Trying to create a file inside /etc or /var/www as a regular user causes an immediate block. The fix depends on intent. For one-off changes to system files, sudo solves it. For web directories where an application writes regularly, the correct path is to adjust ownership:
sudo chown -R www-data:www-data /var/www/html
sudo find /var/www/html -type d -exec chmod 755 {} \;
sudo find /var/www/html -type f -exec chmod 644 {} \;
Accessing another user's files
If maria needs to read files created by joao, there are two clean options. The first is to add maria to joao's group and ensure read access on the group:
sudo usermod -aG joao maria
chmod g+r /home/joao/report.txt
The second uses ACLs for a specific rule without touching groups:
sudo setfacl -m u:maria:r /home/joao/report.txtRead-only filesystem or immutable attribute
In some cases, chmod itself returns Permission denied, even for root. The two most likely causes are: the filesystem was mounted with the ro option, or the file received the i (immutable) attribute via chattr. To diagnose:
mount | grep " on / "
lsattr file
If the file has the i attribute, we remove it with sudo chattr -i file. If the partition is mounted read-only, we remount it with write access or adjust /etc/fstab.
Step-by-step instruction: diagnosis and fix
Whenever a Permission denied appears, we recommend following this sequence. It leads to the right diagnosis without guessing.
Step 1. Identify owner, group, and permissions
ls -l file
ls -ld directory
id
groups
The ls -l command shows the owner, group, and rwx bits. The id command reveals the current user's UID, primary GID, and supplementary groups. Comparing the two, we discover which triplet access is being evaluated against. For even more detailed information, stat provides everything, including timestamps and inode number:
stat fileStep 2. Adjust permissions with chmod
With the cause identified, we adjust permissions. The symbolic form is convenient for targeted changes:
chmod u+x script.sh
chmod g-w file.txt
chmod o=r file.txt
The octal form is better when setting a complete set at once:
chmod 644 config.yaml
chmod 755 deploy.sh
chmod 600 ~/.ssh/id_rsa
chmod 700 ~/.ssh
For recursive adjustments on directory trees, avoid chmod -R with a single value. Use find to separate files and directories:
find /path -type d -exec chmod 755 {} \;
find /path -type f -exec chmod 644 {} \;
Step 3. Fix ownership with chown
When the problem is the file's owner or group, chown solves it:
sudo chown maria file.txt
sudo chown maria:devs file.txt
sudo chown :devs file.txt
sudo chown -R www-data:www-data /var/www
The user:group syntax covers both adjustments in a single operation. The -R flag applies recursively to directories and their contents.
Step 4. Use sudo when it makes sense
sudo is the right tool for operations on system files (/etc, /var/log, /usr) and for starting services that need privileges. For files inside /home or your own projects, prefer fixing ownership with chown over running everything as root, which avoids creating root-owned files in your personal directory.
Step 5. Check additional layers
If POSIX permissions are correct and the error persists, it is time to look at the extra layers. To check ACLs:
getfacl file
If there are entries denying access, adjust with setfacl or remove them with setfacl -b.
To check SELinux on RHEL, AlmaLinux, or Rocky systems:
getenforce
sudo ausearch -m avc -ts recent
If getenforce returns Enforcing and ausearch shows blocks, the SELinux policy is denying access. The fix depends on context, possibly involving restorecon, chcon, or boolean adjustments with setsebool.
To check AppArmor on Ubuntu and Debian, use sudo aa-status. If an active profile restricts the application, the logs in /var/log/syslog or /var/log/audit/audit.log show the denial.
Common mistakes when fixing permissions
We list the slips that cause the most problems and how to avoid them.
Applying chmod 777 on everything is the most tempting fix and the worst idea. It grants read, write, and execute to any user on the system. On exposed servers, it is a recipe for compromise. Identify the specific user or group that needs access and grant only what is necessary.
Using chmod -R 755 on mixed trees adds the x bit unnecessarily on regular files. Use find with -type d and -type f to separate files and directories.
Forgetting the x bit on the parent directory causes failures even with open permissions on the file. Check the entire path with ls -ld at every level.
Reversing the order of chmod and chown in provisioning scripts (Dockerfiles, Ansible) can result in incorrect permissions if chown alters suid and sgid bits. The safe order is chown first, then chmod.
Ignoring SELinux on CentOS, AlmaLinux, and Rocky is a common time-waster. Always run getenforce and ausearch when POSIX adjustments do not fix the issue.
Using sudo where ownership should be fixed creates root-owned files in the wrong places. If an application needs to write to /var/www, set the owner to www-data and let the application run as that user.
Conclusion
Permission denied is less an obstacle and more a clear signal that the system is doing its job. The right approach is to understand which layer caused the block and fix the exact spot.
With practice, decoding a Permission denied message becomes a matter of seconds. The sequence works in any scenario: ls -l and id for diagnosis, chmod to adjust bits, chown to fix ownership, sudo only when it makes sense, and verification of ACLs and SELinux when the rest looks ok.