Skip to content
DevOps AI ToolKit
Newsletter
All guides
AI for Bash & Python Automation By James Joyner IV · · 9 min read Last reviewed Jul 2026

Bash Error Guide: 'Permission denied' — Fix Exec Bits, Ownership, and Mounts

Quick answer

Fix 'Permission denied' in Bash: set the executable bit, correct ownership and ACLs, handle noexec mounts, missing shebangs, and directory traverse permissions.

  • #bash
  • #automation
  • #troubleshooting
  • #errors
Free toolkit

Stuck on this Bash & Python Automation error? Get the free incident triage checklist

A one-page PDF — the exact steps to isolate, fix, and verify a production error like this one. No spam, unsubscribe anytime.

Overview

Permission denied is the kernel telling you the current user lacks the rights for the operation you attempted — executing a file, reading it, writing to it, or traversing a directory to reach it. In Bash automation it shows up in two distinct shapes. The shell’s own message when it cannot execute a file:

bash: ./deploy.sh: Permission denied

And the EACCES error surfaced by a command that could not open or write a path:

./deploy.sh: line 22: /var/log/deploy.log: Permission denied

They look similar but have different fixes: the first is about the executable bit and how the file is invoked; the second is about read/write access to a target path.

Symptoms

  • Running ./script.sh fails immediately with Permission denied, before any script output appears.
  • A script runs, then fails partway when it tries to write a log, PID file, or output directory.
  • The same script works as root/sudo but not as the service user (or vice versa).
  • A file you can ls you cannot cat — or a directory you can see you cannot cd into.
  • Code deployed onto a /tmp, NFS, or removable mount refuses to execute even with the exec bit set.

Common Root Causes

  • Missing executable bit — the file was created, copied, or checked out without +x (common after git on some setups, scp, or unzip).
  • No shebang / wrong interpreter path — invoking ./script with no #! line, or a shebang pointing at an interpreter that isn’t there.
  • Wrong ownership or mode on the target path — the process user cannot write to a log directory, PID file, or output location owned by another user.
  • Missing directory execute (traverse) bit — you need x on every directory in a path to reach a file inside it; a missing x on a parent blocks access even when the file itself is readable.
  • noexec mount option — filesystems like /tmp, /dev/shm, or NFS mounted noexec refuse to execute any file regardless of its mode.
  • ACLs or SELinux/AppArmor — extended ACLs or mandatory access control deny access even when classic rwx bits look correct.
  • Running the wrong user under cron/systemd — the job runs as a user without rights to the paths it touches.
  • Immutable attribute — a file with the i attribute (chattr +i) cannot be written even by root until the attribute is cleared.

Diagnostic Workflow

Start with the file’s mode and ownership, and who you actually are:

ls -l ./deploy.sh
id                      # your uid/gid and groups
namei -l ./deploy.sh    # permissions of every component in the path

namei -l is the fastest way to spot a missing traverse (x) bit on a parent directory. For an execute failure, confirm the exec bit and the shebang:

stat -c '%A %U:%G %n' ./deploy.sh    # human-readable mode + owner
head -n1 ./deploy.sh                 # is there a valid #!/usr/bin/env bash ?

If the mode looks correct but execution still fails, suspect the mount:

findmnt -no OPTIONS -T ./deploy.sh   # look for 'noexec'
mount | grep "$(df --output=target ./deploy.sh | tail -1)"

Check for ACLs and, on RHEL-family systems, SELinux denials:

getfacl ./deploy.sh
lsattr ./deploy.sh                    # look for the 'i' immutable flag
sudo ausearch -m avc -ts recent 2>/dev/null | tail   # SELinux denials

For a write failure, verify the process user can actually write the target directory:

sudo -u deploy test -w /var/log && echo writable || echo "deploy cannot write /var/log"

Example Root Cause Analysis

A CI job checked out a repo and ran ./scripts/migrate.sh, failing with:

/bin/sh: ./scripts/migrate.sh: Permission denied

ls -l told the story:

$ ls -l scripts/migrate.sh
-rw-r--r-- 1 runner runner 1843 Jul  9 02:11 scripts/migrate.sh

The file was mode 644 — readable but not executable. The repo had been checked out on a filesystem that dropped the exec bit, and nobody had chmod +x’d it. Because the CI step invoked the file directly (./scripts/migrate.sh) rather than through an interpreter, the missing x bit was fatal.

Two fixes, one tactical and one durable:

# Tactical: make it executable
chmod +x scripts/migrate.sh

# Durable: record the exec bit in git so it survives checkout everywhere
git update-index --chmod=+x scripts/migrate.sh
git commit -m "Mark migrate.sh executable"

An equally valid workaround that sidesteps the exec bit entirely is to invoke the interpreter explicitly, which only needs read permission on the file:

bash scripts/migrate.sh    # runs even if the exec bit is missing

Prevention Best Practices

  • Commit the exec bit — use git update-index --chmod=+x (or git add with the mode already set) so scripts stay executable across every clone and CI runner.
  • Always include a correct shebang#!/usr/bin/env bash — so the right interpreter is used and the file is self-describing.
  • Run automation as a dedicated user and give that user explicit ownership of the directories it must write (logs, PID files, output), rather than reaching for chmod 777.
  • Never use chmod 777 as a fix — it hides the real ownership problem and opens a security hole; grant the minimum needed instead.
  • Mind noexec mounts — don’t place executables on noexec filesystems like /tmp; if you must, invoke them via bash script.sh or move them to an exec-permitted path.
  • Check ACLs and SELinux when classic bits look right but access is still denied; label files correctly rather than disabling enforcement.
  • Preflight write targets — early in the script, verify the log/output directories are writable and fail fast with a clear message.

Quick Command Reference

ls -l FILE                     # mode and owner
namei -l PATH                  # permissions of every path component
stat -c '%A %U:%G %n' FILE     # readable mode + ownership
chmod +x FILE                  # add the executable bit
git update-index --chmod=+x FILE   # persist exec bit in git
findmnt -no OPTIONS -T FILE    # detect a noexec mount
getfacl FILE                   # inspect ACLs
lsattr FILE                    # detect the immutable (i) attribute
sudo -u USER test -w DIR       # can this user write here?

Conclusion

Permission denied (EACCES) comes down to one question: does the running user have the required right — execute, read, write, or directory traverse — on the specific path involved? Separate the two cases: a failure to run a script is usually a missing exec bit, a bad shebang, or a noexec mount; a failure inside a running script is usually ownership or write permission on a target directory. Diagnose with ls -l, namei -l, and findmnt, fix with the least privilege that works (commit the exec bit, grant the right user ownership), and never paper over it with chmod 777.

Free download · 368-page PDF

Fixed it? Get 500 Bash & Python Automation & DevOps AI prompts — free

500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.

  • 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
  • Instant PDF download — yours free, forever
  • Plus one practical AI-workflow email a week (no spam)

Single opt-in · unsubscribe anytime · no spam.

Did this fix your issue?

Free download · 368-page PDF

Get 500 Battle-Tested DevOps AI Prompts — Free

500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.

  • 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
  • Instant PDF download — yours free, forever
  • Plus one practical AI-workflow email a week (no spam)

Single opt-in · unsubscribe anytime · no spam.