That 'Permission denied' is a liar — here's the fix
You've checked the permissions. You own the file. You're root, for crying out loud. And Linux still says Permission denied. I know this error is infuriating. It's the kind that makes you want to throw your keyboard across the room. But I promise, there's a method to the madness, and it's usually one of four culprits. Let's hunt them down.
The quick checks first
Before we go deep, run these two commands. They'll rule out the boring stuff:
ls -la /path/to/file
mount | grep /path/to/mountpoint
The first shows you the file's mode, owner, and group. The second shows you how the filesystem is mounted. If you see noexec, nodev, nosuid, or ro in the mount options, that's your problem right there. I've seen people pull their hair out over a Permission denied on a script only to find out the partition was mounted noexec — meaning it'll run nothing. That's not a permission problem, that's a mount policy.
If the mount options look fine, move on to the real suspects.
The real fix: check SELinux, ACLs, and immutable flags
The most common reason I see this error in the corporate world — especially on CentOS, RHEL, or Fedora — is SELinux. It's enforcing by default, and it doesn't care if your file is 777. It cares about context. Here's how to check:
ls -Z /path/to/file
sudo ausearch -m AVC -ts recent
The first command shows the SELinux context. The second shows recent denials. If you see something like avc: denied in the audit log, that's SELinux blocking you. The fix isn't to disable SELinux — that's like setting your house on fire to get rid of a spider. Instead, change the context:
sudo chcon -t httpd_sys_content_t /var/www/html/index.html
Or if it's a custom policy, use semanage fcontext to set it permanently. I've fixed countless "permission denied" errors on web server directories with that one command.
Next, check ACLs. If someone set an ACL that restricts access, ls -l won't show it. You need getfacl:
getfacl /path/to/file
If you see a mask:: entry that's too restrictive, or a user/group entry that denies access, fix it with setfacl. For example, to give a user read and write access:
sudo setfacl -m u:username:rw /path/to/file
I've seen ACLs cause this on shared development boxes more times than I can count. One intern's script set an ACL on the project folder, and suddenly the whole team couldn't read it. Took me an hour to figure out.
Finally, check the immutable flag. If someone ran chattr +i on the file, it's locked down tighter than Fort Knox. Even root can't modify it:
lsattr /path/to/file
If you see an i in the output, that's your culprit. Remove it with:
sudo chattr -i /path/to/file
Less common variations of the same issue
Sometimes the problem is more obscure. Here are a few I've run into:
- Bind mounts: If you bind-mount a directory, the mount options from the original filesystem can override what you expect. Check
/proc/mountsforbindentries. - NFS root squash: On NFS, if
root_squashis set, root on the client maps to nobody on the server. That'll give you a nice "Permission denied" even as root. Fix: remount withno_root_squashor use a non-root user. - Filesystem-specific quirks: On btrfs, subvolume permissions are per-subvolume, not inherited. On ZFS, ACLs are handled differently — check with
ls -landgetfaclbut alsozfs get aclinherit. - Systemd sandboxing: If you're running a service under systemd, it might have
ReadOnlyPathsorProtectSystemset, which blocks access regardless of file permissions. Check the unit file.
Prevention: set it and forget it
The best way to avoid this mess is to think about context before you copy files around. When you're deploying a web app, use semanage fcontext to set the right context before you even place the files. When you're sharing a directory with teammates, set ACLs deliberately with setfacl -R -m g:devs:rwx and walk away. And never, ever use chmod 777 as a band-aid. It's the lazy fix, and it's the reason I've had to clean up security holes in production servers. Do the work upfront, and you'll stop seeing this error for good.
And if you're still stuck, remember: strace is your friend. Run strace -f -e trace=file on the command that's failing. It'll show you exactly which syscall fails and why. I've debugged more than a few "impossible" permission errors that way.