The 30-Second Fix: Make It Executable
If you're staring at bash: ./script.sh: Permission denied, the culprit is almost always missing execute permission. The fix takes two seconds:
chmod +x script.sh
./script.sh
That's it. If it runs, you're done. This is the #1 cause, especially when you've copied a script from Windows or extracted it from a zip archive.
Why does this happen? By default, new files get permissions like rw-r--r-- (644). That means you can read and write, but not execute. The chmod +x adds execute permission for the owner, group, and others. Simple.
Pro tip: If you seePermission deniedfor a script you just wrote, check the permissions first withls -l script.sh. You'll see-rw-r--r--— missing thex. That's your smoking gun.
The 5-Minute Fix: Check the Shebang and Mount Options
If chmod +x didn't help, or the script still won't run, you're looking at one of two things: a broken shebang or a noexec mount. Let's deal with both.
Broken Shebang
The shebang line (the very first line of the script, like #!/bin/bash) tells the kernel which interpreter to use. If that path is wrong, you'll get a cryptic error. For instance:
#!/usr/bin/env python3
# script content...
If env can't find python3, you'll see env: ‘python3’: No such file or directory — but sometimes you get Permission denied if the interpreter itself is not executable. Check that the interpreter exists and is executable:
which bash
ls -l /bin/bash
Also, make sure the shebang is actually the first line, with no leading spaces or BOM (byte-order mark). A BOM from a Windows text editor will break the shebang and cause weird errors like #!/bin/bash — that will give you bad interpreter: No such file or directory.
noexec Mount
Now the sneaky one. If your script lives on a filesystem mounted with the noexec option, you can't execute any binaries from it — even if permissions are correct. This happens a lot on /tmp or separate data partitions. Check your mounts:
mount | grep noexec
If your script's directory shows up with noexec, you have two options:
- Move the script to a partition that allows execution (like
/homeor/usr/local/bin). - Remount the partition with
exec(requires root):mount -o remount,exec /path— but that's temporary and might not survive a reboot.
Real-world trigger: I've seen this bite people on Docker containers and shared hosting where /tmp is noexec. They put a script there, try to run it, and get a confusing permission denied.
The 15-Minute Fix: Dig Deeper When Nothing Else Works
If you've gone through the first two steps and still get Permission denied, it's time to check the usual suspects that only bite after you've eliminated the obvious.
Filesystem ACLs (Access Control Lists)
Standard chmod permissions might be fine, but an access control list could be overriding them. Check with:
getfacl script.sh
If you see entries like user:someuser:--- or mask::r--, an ACL is blocking execution. Remove or adjust the ACL:
setfacl -b script.sh # remove all ACLs
That resets the ACL and should let the file's regular permissions take over. This is rare on home systems, but on corporate servers with ACL-heavy setups, it's a common gotcha.
SELinux or AppArmor Policies
On RHEL/CentOS and Fedora, SELinux can block script execution even if the file is executable. Check if SELinux is enforced:
getenforce
If it says Enforcing, look at the audit logs:
audit2why -a | grep -i 'denied'
You might see avc: denied { execute } for your script. The fix is usually to set the correct context:
restorecon -v script.sh
Or if it's in a home directory, you might need to adjust the SELinux boolean for user scripts (rare). For AppArmor (Ubuntu), check aa-status and /var/log/syslog for denials. The fix is usually a profile tweak or disabling enforcement for that specific program.
File System Corruption or Quotas
I've seen this once in a blue moon: a file with weird permissions due to filesystem issues. Check for read-only mounts or quota limits:
mount | grep '(ro)'
df -h
quota -u yourusername
If the filesystem is read-only, you can't execute anything new. Remount with rw if it's safe. Quotas are less likely to block execution, but hitting an inode limit can cause weird errors.
One More: Check Your Shell's Hash Table
This is a throwback, but if you've used a command name before the script existed, your shell might have cached the old path. Try
hash -r
or just close and reopen the terminal. It's a long shot, but I've seen it waste 10 minutes of someone's day.
When All Else Fails: Try Running with an Explicit Interpreter
If you're in a bind and just need the script to run regardless of execute permissions, you can bypass the permission check by invoking the interpreter directly:
bash script.sh
Or for Python:
python3 script.py
That works because the kernel isn't executing the script — bash or python3 is. But don't use that as a crutch. Fix the underlying issue, because scripts that need to run via cron or from other contexts will still fail.
Bottom line: 90% of the time it's chmod +x. When it's not, check the shebang and mount options. And if you're on a locked-down corporate box, SELinux and ACLs are your next suspects. That's the whole playbook.