Skip to content
ohsudo
Linux

"Permission denied" when running a script: 5 causes and fixes

Fix "bash: ./script.sh: Permission denied" and "bad interpreter" on Linux: execute bits, noexec mounts, directory permissions, Windows line endings and shebangs.

Beginner4 min readApplies to Ubuntu · Debian · RHEL / Rocky / AlmaLinux · Bash 4+

by OhSudo ·

You wrote (or downloaded) a script, you run it, and Linux says no:

-bash: ./backup.sh: Permission denied
sudo: unable to execute ./backup.sh: Permission denied
-bash: ./deploy.sh: /bin/bash^M: bad interpreter: No such file or directory

Running a file as a program needs more than the file existing: it needs the execute permission, a filesystem that allows executing, access to every directory on the way, and a valid interpreter line. Here’s how to check each one, fastest first.

TL;DR

Terminal window
ls -l script.sh # is there an x in the permissions?
chmod u+x script.sh # add it for the owner
findmnt -no OPTIONS --target script.sh # "noexec" here blocks every script on that filesystem
bash script.sh # run it through the interpreter instead
cat -A script.sh | head -3 # ^M at line ends = Windows line endings
sed -i 's/\r$//' script.sh # convert them to Unix line endings

1. The file isn’t executable

The most common cause by far. Look at the permissions:

Terminal window
ls -l scripts/backup.sh
-rw-r--r-- 1 guest guest 132 Nov 1 10:00 scripts/backup.sh

There’s no x, so nobody may execute it — not even the owner. Add the execute permission for yourself:

Terminal window
chmod u+x scripts/backup.sh
./scripts/backup.sh

chmod +x would add it for everyone (owner, group and others). Prefer u+x unless other users really need to run the script. Not sure what a mode like 750 means? The chmod Calculator shows it.

Tip

Scripts in a Git repository lose the execute bit when the repository is used from Windows, or when the bit was never committed. Store it in Git once with git update-index --chmod=+x scripts/backup.sh and commit — everyone who clones gets an executable script.

2. The filesystem is mounted with noexec

The file is executable, you’re the owner, and it still says “Permission denied”? Check where it lives. Directories like /tmp, /dev/shm, USB drives and, on hardened servers, even /home are often mounted with the noexec option, which forbids running any program from them:

Terminal window
findmnt -no OPTIONS --target /tmp
rw,nosuid,nodev,noexec,relatime

noexec is a security measure (attackers like to drop programs in /tmp), so don’t remove it. Instead:

  • run the script through its interpreter — this only needs read permission:

    Terminal window
    bash /tmp/install.sh
  • or move the script somewhere that allows executing, such as your home directory or /usr/local/bin.

3. A directory on the path blocks you

To run /opt/tools/bin/check.sh, you need the execute (x, “search”) permission on every directory of the path: /opt, /opt/tools and /opt/tools/bin. One locked directory is enough to get “Permission denied”, even if the script itself is perfect. namei shows the permissions of each step at once:

Terminal window
namei -l /opt/tools/bin/check.sh

Fix the directory that’s missing the x for you (for example sudo chmod o+x /opt/tools), or ask its owner to.

4. “bad interpreter: No such file or directory” — Windows line endings

This one confuses everybody, because the file obviously exists:

-bash: ./deploy.sh: /bin/bash^M: bad interpreter: No such file or directory

The script was saved with Windows line endings (CRLF). Every line ends with an invisible carriage return, ^M — so Linux looks for an interpreter called /bin/bash␍, which doesn’t exist. Make the invisible characters visible:

Terminal window
cat -A scripts/deploy.sh | head -3
#!/bin/bash^M$
echo "Deploying webapp..."^M$
echo "Deployed."^M$

Remove the carriage returns:

Terminal window
sed -i 's/\r$//' scripts/deploy.sh
./scripts/deploy.sh

(dos2unix scripts/deploy.sh does the same, if it’s installed.) If the shebang is #!/usr/bin/env bash, the same problem shows up as /usr/bin/env: 'bash\r': No such file or directory.

Tip

Stop it from coming back: in Git, add a .gitattributes file with the line *.sh text eol=lf, and set your editor to use LF line endings for shell scripts.

5. The shebang points to an interpreter that isn’t there

The same “bad interpreter” message, without the ^M, means the first line names a program that doesn’t exist on this machine — for example #!/usr/local/bin/bash on a server where Bash lives in /usr/bin/bash, or a Python version that isn’t installed:

Terminal window
head -1 script.sh # what does the script ask for?
command -v bash # where is bash on this machine?

#!/usr/bin/env bash finds bash wherever it is in the PATH, which makes scripts portable between distributions.

Note

RHEL / Rocky / AlmaLinux: if a script runs fine from your shell but fails when a service runs it (systemd, a web server, cron jobs run by a confined service), SELinux may be blocking it. Check the audit log with sudo ausearch -m avc -ts recent, look at file labels with ls -Z, and restore the default labels with sudo restorecon -Rv /path/to/scripts. On Ubuntu and Debian, AppArmor plays a similar role for some services.

Quick checklist

Symptom Cause Fix
Permission denied, no x in ls -l Not executable chmod u+x script.sh
Permission denied, x is there noexec mount bash script.sh, or move it
Permission denied for a script in another user’s tree Directory without x namei -l path, fix that directory
bad interpreter with ^M Windows line endings sed -i 's/\r$//' script.sh
bad interpreter without ^M Wrong shebang #!/usr/bin/env bash

$ ./practice.sh

Practice this in the terminal

Open the Terminal Playground with this article’s scenario loaded. It is safe and runs in your browser.

Open the terminal →

Found a mistake? Let us know at contact@ohsudo.com.

Tools

Try it yourself

Articles

Related articles