Fix: bash: ./script.sh: Permission denied (Linux, macOS, WSL)
The Error
You try to run a script or access a file and get:
bash: ./script.sh: Permission deniedOr one of these variations:
-bash: ./deploy.sh: Permission denied
zsh: permission denied: ./script.sh
EACCES: permission denied, open '/path/to/file'
cp: cannot create regular file '/some/path': Permission denied
mkdir: cannot create directory '/some/path': Permission deniedWhy This Happens
Every file and directory on Linux and macOS has three sets of permissions: owner, group, and others. Each set controls read (r), write (w), and execute (x) access.
When you see “Permission denied,” the operating system is telling you that your user account lacks the required permission for the operation you attempted. The most common causes:
- The file is not executable. You downloaded or created a script, but the execute bit is not set.
- You don’t own the file. Another user (often
root) owns it, and the permissions for “others” don’t include what you need. - The directory is not writable. You’re trying to create or modify a file in a directory you don’t have write access to.
- A security module is blocking access. SELinux, AppArmor, or filesystem ACLs are denying access even though standard Unix permissions allow it.
You can inspect a file’s permissions with:
ls -la script.shOutput like this:
-rw-r--r-- 1 root root 1024 Mar 15 10:00 script.shThis tells you: the file is owned by root, the group is root, and the permissions are rw-r--r-- — the owner can read and write, everyone else can only read. Nobody has execute permission.
Fix 1: Make the Script Executable (chmod +x)
This is the most common fix. You have a script but the execute permission is not set:
chmod +x script.shNow run it:
./script.shIf you want to set permissions more precisely, use the numeric form:
# Owner: read+write+execute, Group: read+execute, Others: read+execute
chmod 755 script.sh
# Owner: read+write+execute, Group and Others: no access
chmod 700 script.shQuick reference for numeric permissions:
| Number | Permission |
|---|---|
| 7 | read+write+execute |
| 6 | read+write |
| 5 | read+execute |
| 4 | read only |
| 0 | no access |
Why your script lost the execute bit
- Git does not always preserve execute bits. If someone committed a script without the execute bit, everyone who clones the repo gets a non-executable file. Fix it and tell Git to track the permission:
git update-index --chmod=+x script.sh. If you’re having other Git issues, see Fix: git fatal: not a git repository. - Downloading from the web (via a browser or
curl/wget) never sets the execute bit. You always need tochmod +xafter downloading. - Copying from a Windows/NTFS/FAT32 filesystem strips execute bits because those filesystems don’t support Unix permissions.
- Extracting from a zip file may not preserve permissions depending on the archiver. Tar archives (
tar.gz,tar.bz2) preserve them; zip files often don’t.
Fix 2: Run the Script Through the Interpreter
If you can’t or don’t want to change permissions, run the script by passing it directly to the interpreter:
bash script.sh
python3 script.py
node script.jsThis works because you only need read permission on the file — the interpreter reads it and executes the content. You’re not executing the file itself.
Fix 3: Fix File Ownership (chown)
If the file is owned by another user (typically root), change ownership:
sudo chown $USER:$USER script.shFor an entire directory and its contents:
sudo chown -R $USER:$USER /path/to/directoryCheck ownership before and after:
ls -la script.shWhen to use chown vs chmod: Use chown when the wrong user owns the file. Use chmod when the right user owns the file but the permission bits are wrong.
Fix 4: Fix Directory Permissions
Sometimes the error is not about the file itself but the directory containing it. You need execute permission on a directory to access anything inside it, and write permission to create or delete files in it:
# Make a directory accessible
chmod 755 /path/to/directory
# Make a directory and everything in it writable by you
sudo chown -R $USER:$USER /path/to/directoryA common trap: you have permission on the file but not on a parent directory in the path. Check every directory in the path:
namei -l /full/path/to/fileThis prints the ownership and permissions of every component in the path, making it easy to spot which directory is blocking access.
Fix 5: Using sudo (and When Not To)
sudo runs a command as root, bypassing all permission checks:
sudo ./script.sh
sudo mkdir /opt/myapp
sudo cp config.yaml /etc/myapp/When sudo is appropriate:
- Writing to system directories (
/etc,/usr,/var) - Installing system packages (
apt,dnf,pacman) - Managing system services (
systemctl)
When sudo is NOT appropriate:
- npm global installs — use nvm or change the npm prefix instead. See Fix: EACCES permission denied when installing npm packages globally.
- Docker commands — add your user to the
dockergroup instead. See Fix: Docker Permission Denied While Trying to Connect to the Docker Daemon Socket. - Anything in your home directory — if files in
~are owned by root, fix them withchown, not by running everything as root. - Development servers or build tools — running
sudo npm startorsudo python app.pycreates root-owned files that cause further permission problems.
Warning: Never run package managers like npm, pip, or gem with sudo unless you’re deliberately installing into a system directory. These tools execute arbitrary code from packages, and running them as root is a security risk.
Fix 6: Fix the Shebang Line
If chmod +x is set but you still get “Permission denied” or “bad interpreter,” check the first line of your script (the shebang):
head -1 script.shA correct shebang looks like:
#!/bin/bash
#!/usr/bin/env bash
#!/usr/bin/env python3Common problems:
Wrong line endings (Windows CRLF): If the file was created or edited on Windows, it may have \r\n line endings. The system tries to find an interpreter called bash\r, which doesn’t exist:
# Check for Windows line endings
file script.sh
# If it says "with CRLF line terminators":
dos2unix script.sh
# Or without dos2unix:
sed -i 's/\r$//' script.shMissing shebang: Without a shebang, the system tries to execute the file with the default shell, which may not understand the script’s syntax. Always add a shebang as the first line.
Incorrect interpreter path: /bin/bash doesn’t exist on some systems (certain containers, NixOS). Use #!/usr/bin/env bash for portability — it finds bash wherever it’s installed.
Fix 7: EACCES in Node.js File Operations
Node.js throws EACCES when your process lacks permission to read, write, or access a file or directory:
Error: EACCES: permission denied, open '/var/log/app.log'
Error: EACCES: permission denied, mkdir '/opt/myapp/data'
Error: EACCES: permission denied, access '/usr/local/lib/node_modules'For npm global install errors, see Fix: EACCES permission denied when installing npm packages globally.
For file operation errors in your own code:
- Check that the target path exists and your user owns it:
ls -la /var/log/app.log- If writing to a system directory, create a dedicated directory owned by your app user:
sudo mkdir -p /var/log/myapp
sudo chown $USER:$USER /var/log/myapp- If running as a service (systemd), make sure the
User=directive in the unit file matches the owner of the directories the app needs:
[Service]
User=myapp
Group=myapp
ReadWritePaths=/var/log/myapp /var/lib/myapp- Port binding below 1024: If your Node.js app gets
EACCESwhen trying to listen on port 80 or 443, unprivileged users can’t bind to ports below 1024. Use a reverse proxy like Nginx on ports 80/443 and run your app on a high port (3000, 8080), or grant the capability:
sudo setcap 'cap_net_bind_service=+ep' $(which node)Fix 8: Docker Volume Permission Issues
Files created inside Docker containers often end up owned by root on the host, or host files are inaccessible inside the container:
# Container can't write to mounted volume
docker run -v $(pwd)/data:/app/data myimage
# Error: EACCES: permission denied, open '/app/data/output.txt'Fix: Match the container user to your host user:
docker run -u $(id -u):$(id -g) -v $(pwd)/data:/app/data myimageFix in Dockerfile: Create a non-root user in your image:
RUN groupadd -g 1000 appuser && \
useradd -u 1000 -g appuser -m appuser
USER appuserFix with Docker Compose:
services:
app:
image: myimage
user: "${UID}:${GID}"
volumes:
- ./data:/app/dataFix: Reset ownership of files created by containers:
sudo chown -R $USER:$USER ./dataFor more Docker permission issues, see Fix: Docker Permission Denied While Trying to Connect to the Docker Daemon Socket.
Still Not Working?
SELinux is blocking access
On Fedora, RHEL, CentOS, and Amazon Linux, SELinux may deny access even when standard Unix permissions allow it. Check for SELinux denials:
# Check if SELinux is enforcing
getenforce
# Search for recent denials
sudo ausearch -m avc -ts recent
# Check the audit log directly
sudo grep denied /var/log/audit/audit.log | tail -5Temporarily disable SELinux to confirm it’s the cause:
sudo setenforce 0
# Try your command again
sudo setenforce 1 # re-enable immediatelyFor a permanent fix, use the appropriate SELinux context. For example, to let a web server read files:
sudo semanage fcontext -a -t httpd_sys_content_t "/var/www/myapp(/.*)?"
sudo restorecon -Rv /var/www/myappFor Docker volumes on SELinux systems, add the :z or :Z flag to the volume mount:
docker run -v $(pwd)/data:/app/data:z myimageAppArmor is blocking access
On Ubuntu and Debian, AppArmor can block access silently. Check for denials:
sudo dmesg | grep -i apparmor | tail -10
sudo aa-statusTemporarily put a profile in complain mode:
sudo aa-complain /usr/sbin/nginxThe file has immutable attributes (chattr)
A file can be marked immutable, preventing even root from modifying or deleting it:
# Check for special attributes
lsattr script.shIf you see i in the output (e.g., ----i---------e--- script.sh), the file is immutable:
# Remove the immutable flag
sudo chattr -i script.shThis is uncommon but occasionally set on critical system files or by security hardening scripts.
Access Control Lists (ACLs) are overriding permissions
Standard ls -l permissions might look fine, but an ACL could be denying access. A + at the end of the permissions string indicates ACLs are set:
-rwxr-xr-x+ 1 user user 1024 Mar 15 10:00 script.shView the ACL:
getfacl script.shRemove all ACLs to fall back to standard permissions:
sudo setfacl -b script.shOr grant your user explicit access:
sudo setfacl -m u:$USER:rwx script.shThe filesystem is mounted noexec
Some systems mount /tmp, /home, or external drives with the noexec option, which prevents executing any files on that filesystem regardless of file permissions:
# Check mount options
mount | grep noexec
# Or:
findmnt -t ext4,xfs,btrfs,tmpfsIf your script is on a noexec mount, you have two options:
- Move the script to a filesystem without
noexec:
cp /tmp/script.sh ~/script.sh
chmod +x ~/script.sh
~/script.sh- Run it through the interpreter (bypasses noexec):
bash /tmp/script.sh- Remount without noexec (if you control the system):
sudo mount -o remount,exec /tmpumask is creating files with restrictive permissions
Your shell’s umask controls the default permissions for newly created files. An overly restrictive umask can cause permission issues:
# Check current umask
umaskCommon values:
0022— default. New files get 644 (rw-r—r—), new directories get 755.0077— restrictive. New files get 600 (rw-------), new directories get 700. Other users can’t access anything you create.0002— permissive. New files get 664, new directories get 775. Group members can write.
If files you create are not accessible to others (or to services running as a different user), check if your umask is 0077. Change it in ~/.bashrc or ~/.zshrc:
umask 0022Windows WSL permission issues
WSL has its own permission model that bridges Windows and Linux. Common problems:
Files on Windows drives (/mnt/c) always show 777 or always deny access:
By default, WSL maps all Windows files to a single set of permissions. To enable proper Linux permissions on Windows drives, add to /etc/wsl.conf:
[automount]
enabled = true
options = "metadata,umask=022,fmask=133"Then restart WSL:
wsl --shutdownScripts from Windows have wrong line endings:
dos2unix script.shchmod doesn’t seem to work on /mnt/c:
The metadata mount option (shown above) is required for chmod to work on Windows drives. Without it, permission changes are silently ignored.
File permissions work differently inside vs outside the WSL filesystem:
Store your development files inside the WSL filesystem (~/projects/) rather than on the Windows mount (/mnt/c/Users/...). The Linux filesystem supports proper permissions, is faster, and avoids line-ending issues.
SSH key or config file has wrong permissions
SSH is strict about file permissions and silently refuses to use files that are too open:
chmod 700 ~/.ssh
chmod 600 ~/.ssh/id_ed25519
chmod 644 ~/.ssh/id_ed25519.pub
chmod 600 ~/.ssh/config
chmod 600 ~/.ssh/authorized_keysIf you’re getting “Permission denied (publickey)” when using Git over SSH, see Fix: Permission denied (publickey) — Git SSH Authentication Failed.
NFS or network-mounted filesystems
Network filesystems (NFS, CIFS/SMB) often use root_squash, which maps root access to an unprivileged user. sudo may not help on NFS mounts:
# Check if the mount is NFS
mount | grep nfsOn NFS with root_squash (the default), even sudo chown will fail. The fix depends on server-side NFS export configuration. Contact the filesystem administrator or use no_root_squash in the NFS exports (security tradeoff).
Snap package confinement
Snap-installed applications run in a sandboxed environment and cannot access files outside their confinement. If a snap-packaged tool gives “Permission denied” on files in unexpected locations:
# Check if the tool is a snap
which tool-name
snap list | grep tool-nameSnaps can typically only access files under ~/ and explicitly connected interfaces. You may need to connect additional interfaces:
sudo snap connect myapp:home
sudo snap connect myapp:removable-mediaRelated: Fix: Docker Permission Denied While Trying to Connect to the Docker Daemon Socket | Fix: EACCES permission denied when installing npm packages globally | Fix: Permission denied (publickey) — Git SSH Authentication Failed
Related Articles
Fix: python: command not found (or python3: No such file or directory)
How to fix 'python: command not found', 'python3: command not found', and wrong Python version errors on Linux, macOS, Windows, and Docker. Covers PATH, symlinks, pyenv, update-alternatives, Homebrew, and more.
Fix: EACCES permission denied when installing npm packages globally
How to fix 'Error: EACCES: permission denied, access /usr/local/lib/node_modules' when running npm install -g on macOS or Linux. Multiple solutions ranked by recommendation.
Fix: ERROR: Could not build wheels / Failed building wheel (pip)
How to fix pip 'ERROR: Could not build wheels', 'Failed building wheel', 'No matching distribution found', and 'error: subprocess-exited-with-error'. Covers missing C compilers, build tools, system libraries, Python version issues, pre-built wheels, and platform-specific fixes for Linux, macOS, and Windows.
Fix: Permission denied (publickey) – Git SSH Authentication Failed
How to fix 'git@github.com: Permission denied (publickey)' and 'fatal: Could not read from remote repository' when pushing or cloning over SSH. Covers key generation, ssh-agent, GitHub/GitLab setup, and edge cases.