# Linux "Permission Denied" — Diagnose It Fast

URL: https://www.devopsness.com/blog/fix-permission-denied-linux
Author: Kiril Urbonas
Published: 2026-07-22
Updated: 2026-07-22
Categories: Linux
Tags: Security, Linux, Permissions, Troubleshooting, Sysadmin

Summary: A likelihood-ordered checklist for tracing "Permission denied" on Linux through mode bits, ownership, ACLs, SELinux, and mount options.

---
"Permission denied" is one error string covering at least seven different failures. The trick to fixing it fast is to check them in order of likelihood instead of guessing. Below is the order I actually work through on a live box, from the boring-and-common down to the exotic.

Resist the urge to `chmod 777` your way out. It usually does fix the symptom, which is exactly why it is dangerous: you never learn what was wrong, and you leave a world-writable file behind for the next person to find. Diagnose first.

## 1. Mode bits and ownership

Start with the file itself. Who owns it, and who are you?

```bash
$ ls -l deploy.conf
-rw-r----- 1 root deploy 812 Jul 20 14:02 deploy.conf
$ id
uid=1001(alice) gid=1001(alice) groups=1001(alice),27(sudo)
```

Read the mode left to right: `-rw-r-----`. Owner (`root`) gets read/write, group (`deploy`) gets read, everyone else gets nothing. You are `alice`, not `root`, and you are not in the `deploy` group. So you land in the "other" bucket with zero permissions. That is your denial.

The fix is to grant the right access to the right principal, not to open the file to the world. Add yourself to the group, or adjust group ownership:

```bash
$ sudo usermod -aG deploy alice   # then log out/in for it to take
$ sudo chgrp deploy deploy.conf   # or fix group ownership instead
```

The three fields that matter every time: the mode string, the owner (third column), and the group (fourth column). Compare them against `id` output. Ninety percent of denials end here.

## 2. Directory execute and the traversal bit

On a directory, the execute bit means "may traverse," not "may run." Without `x` on a directory you cannot enter it, cannot `cd` into it, and cannot reach anything inside it even if the target file is `0777`.

The part people miss: **every parent** in the path needs `x` for you. A permission problem three levels up denies the leaf.

```bash
$ cat /srv/app/secrets/token
cat: /srv/app/secrets/token: Permission denied
$ namei -l /srv/app/secrets/token
f: /srv/app/secrets/token
 drwxr-xr-x root root /
 drwxr-xr-x root root srv
 drwxr-x--- root app  app
 drwxr-xr-x root app  secrets
 -rw-r--r-- root app  token
```

`namei -l` walks the full path and prints the mode of each component. The `token` file is world-readable, but `app` is `drwxr-x---` and you are not `root` or in `app`. You cannot traverse `app`, so you never reach `token`. This is the single most useful command for "but the file permissions look fine" cases.

## 3. Missing execute on a script

If you are trying to run something and get denied, check the execute bit on the file itself.

```bash
$ ./backup.sh
bash: ./backup.sh: Permission denied
$ ls -l backup.sh
-rw-r--r-- 1 alice alice 240 Jul 21 09:10 backup.sh
$ chmod +x backup.sh
```

Note the distinct signal: the shell prefixes the error with `bash:` and the path. That is the kernel refusing to `exec` a file without an execute bit. A related lookalike is `bash: ./backup.sh: cannot execute: required file not found`, which is usually a bad shebang line or CRLF line endings, not a permission issue at all. Read the exact wording before reaching for `chmod`.

## 4. ACLs

If `ls -l` shows a trailing `+` on the mode string, standard bits are not the whole story. There are POSIX ACLs layered on top.

```bash
$ ls -l report.csv
-rw-rw----+ 1 finance finance 1841 Jul 19 22:31 report.csv
$ getfacl report.csv
# file: report.csv
# owner: finance
# group: finance
user::rw-
user:alice:---          # explicit deny for alice
group::rw-
mask::rw-
other::---
```

The base group bits say `rw-`, so you would expect group members to read it. But there is an explicit `user:alice:---` entry stripping your access. ACLs let you deny a single user regardless of group membership, and `ls -l` only hints at it with that `+`. Grant access with `setfacl`:

```bash
$ sudo setfacl -m u:alice:rw report.csv
```

Also watch the `mask` line. The effective permission for named users and the group is the ACL entry ANDed with the mask, so a restrictive mask can silently clamp everything below it.

## 5. SELinux and AppArmor

Here is the classic tell: the mode bits are correct, ownership is correct, no ACLs, and it still fails. Then someone runs `setenforce 0` and it works. That is SELinux, not permissions.

```bash
$ sudo cat /var/www/html/index.html
cat: /var/www/html/index.html: Permission denied
$ ls -Z /var/www/html/index.html
-rw-r--r--. root root unconfined_u:object_r:user_home_t:s0 index.html
```

The `.` after the mode means an SELinux context is present. That context is `user_home_t`, which is wrong for something the web server should serve (it wants `httpd_sys_content_t`). The file was probably `mv`'d from a home directory, carrying its old label. Confirm with the audit log:

```bash
$ sudo ausearch -m avc -ts recent
type=AVC msg=audit(...): avc:  denied  { read } for  pid=1123 comm="httpd"
  name="index.html" dev="sda1" ino=54217
  scontext=system_u:system_r:httpd_t:s0
  tcontext=unconfined_u:object_r:user_home_t:s0 tclass=file permissive=0
$ sudo sealert -a /var/log/audit/audit.log   # human-readable suggestions
$ sudo restorecon -v /var/www/html/index.html # reset to policy default
```

`ausearch -m avc` shows the denial; `sealert` explains it and suggests a fix; `restorecon` relabels to the policy default, which fixes most of these. Reach for `semanage fcontext` only when the default is genuinely wrong for your layout. Do not leave SELinux disabled as a "fix."

On Debian and Ubuntu the equivalent is AppArmor. Check `sudo aa-status` and look in `dmesg` or the journal for `apparmor="DENIED"` lines naming the profile and the path it blocked.

## 6. Mount options

If a whole filesystem behaves oddly, the mount itself may be the constraint. `noexec` blocks running any binary from that mount; `ro` blocks all writes no matter what the bits say.

```bash
$ ./tool
bash: ./tool: Permission denied
$ findmnt -no OPTIONS -T ./tool
rw,nosuid,nodev,noexec,relatime
```

There it is: `noexec` on this mount, common on `/tmp`, `/home`, and removable media hardened by policy. The file has its `x` bit, but the mount forbids execution. Similarly a write to a read-only mount fails with `Read-only file system` rather than the generic message. `findmnt -T <path>` tells you which mount a path lives on and its options.

## 7. Writing to a file you can only read

A subtle one: you can `cat` a file but `>>` fails. Read and write are separate bits, and you may match a class that grants one but not the other.

```bash
$ ls -l app.log
-rw-r--r-- 1 root adm 9210 Jul 21 11:44 app.log
$ echo x >> app.log
bash: app.log: Permission denied
```

You are neither `root` (owner, `rw`) nor in `adm` (group, `r`), so you fall to "other," which is read-only. Reading works, appending does not. The mental model: the kernel picks exactly one class for you (owner, else group, else other) and applies only that class's bits. It does not fall through to a more permissive one.

## The call we'd make

Work top to bottom and stop at the first hit. Nine times in ten it is a mode-bit or ownership mismatch you can read straight out of `ls -l` and `id`; `namei -l` catches the parent-directory traversal cases. Only after those come out clean should you suspect the `+` of an ACL or the `.` of an SELinux label. Fix the specific principal that is being denied. Do not `chmod 777`, do not leave `setenforce 0` running, and relabel with `restorecon` rather than disabling policy.

For the broader methodology, see the [Linux troubleshooting guide](/blog/linux-troubleshooting-guide), and for a deeper reference on the bits themselves read [Linux file permissions explained](/blog/linux-file-permissions-explained).

