Linux & OS Intermediate

Fixing ‘Permission Denied’ for Bash Scripts on Alpine Linux

Resolve 'Permission denied' errors when running shell scripts on Alpine Linux. Troubleshoot execute permissions, shebang issues with Bash, and 'noexec' mounts.

👨‍💻
Senior Systems Architect • Verified in Staging Labs

Resolve 'Permission denied' errors when running shell scripts on Alpine Linux. Troubleshoot execute permissions, shebang issues with Bash, and 'noexec' mounts.

When deploying or executing shell scripts on Alpine Linux, encountering a "Permission denied" error can be a frustrating roadblock. While often a simple file permission issue, Alpine's lightweight nature and default BusyBox ash shell introduce unique considerations, especially when dealing with scripts intended for bash. As an expert with 16 years of web hosting and DevOps experience, I'll guide you through a comprehensive troubleshooting process to diagnose and resolve these execution errors.

Symptom & Error Signature

The most common symptom is the failure of a script to execute, accompanied by an explicit error message in your terminal or application logs.

Typical error output you might see:

$ ./my-script.sh
bash: ./my-script.sh: Permission denied

Or, if bash is not the default interpreter or not found:

$ ./my-script.sh
/bin/bash: Permission denied # This might specifically indicate an issue with the bash binary itself or its path

Sometimes, the error might appear without the bash: prefix, especially if sh is attempting to execute it:

$ ./my-script.sh
./my-script.sh: Permission denied

You can confirm the file's current permissions using ls -l:

$ ls -l my-script.sh
-rw-r--r-- 1 user group 1234 Sep  8 10:00 my-script.sh

Notice the absence of the 'x' (execute) bit for any of the permission sets (user, group, others).

Root Cause Analysis

"Permission denied" for a shell script on Alpine Linux typically stems from one or more of the following fundamental issues:

  1. Missing Execute Permission (File Mode Bits): This is the most common reason. Linux files have permission bits for read (r), write (w), and execute (x) for the owner, group, and others. If the execute bit is not set for the user attempting to run the script, execution will be denied.
  2. Incorrect Shebang (#!) or Interpreter Availability: Alpine Linux, by default, uses BusyBox ash as its /bin/sh. bash is not installed by default. If your script begins with #!/bin/bash and bash is not installed or not found at that exact path within the Alpine environment, the system won't know how to execute the script, leading to a "Permission denied" or "command not found" error for the interpreter itself.
  3. Filesystem Mounted with noexec Option: The filesystem or volume where your script resides might be mounted with the noexec option. This security measure prevents any executable code (including scripts) from running from that specific mount point, regardless of the individual file's permissions. This is particularly common in Docker containers with volume mounts.
  4. Windows Line Endings (CRLF): While less common for direct "Permission denied" errors, scripts transferred from Windows environments often retain Carriage Return Line Feed (CRLF) line endings instead of the Unix-standard Line Feed (LF). This can cause the shebang line to be misinterpreted (e.g., #!/bin/bash^M instead of #!/bin/bash), leading to interpreter path errors or execution failures.

Step-by-Step Resolution

Follow these steps sequentially to diagnose and resolve your script execution issue on Alpine Linux.

1. Verify and Set Execute Permissions

The first and most fundamental step is to ensure the script has the necessary execute permission.

  1. Check current permissions: Use the ls -l command to inspect the file's permission bits. Look for the x character in the permission string (-rwxr-xr-x).

    ls -l my-script.sh
    

    If you see something like -rw-r--r--, the execute bit is missing.

  2. Add execute permission: Use the chmod command to add the execute permission.

    chmod +x my-script.sh
    

    This command adds the execute permission for the owner, group, and others (a+x). If you prefer more granular control, you can use:

    • chmod u+x my-script.sh (add execute for owner)
    • chmod go+x my-script.sh (add execute for group and others)

    After applying chmod, verify the permissions again:

    ls -l my-script.sh
    # Expected output: -rwxr-xr-x 1 user group 1234 Sep  8 10:00 my-script.sh
    
  3. Attempt execution: Try running your script again.

    ./my-script.sh
    

2. Confirm Shebang (#!) and Interpreter Availability on Alpine

If the script still fails with a permission or interpreter-related error after setting execute permissions, the shebang line and the availability of the specified interpreter are the next areas to investigate. This is especially crucial on Alpine.

  1. Examine the shebang line: Open your script and check the very first line.

    head -n 1 my-script.sh
    
    • If it's #!/bin/sh: This usually points to /bin/sh, which on Alpine is BusyBox ash. Most POSIX-compliant shell scripts will run fine.
    • If it's #!/bin/bash: This indicates the script explicitly requires bash.
  2. Check interpreter availability:

    • For #!/bin/sh: Alpine's default /bin/sh (BusyBox ash) is almost always present.

    • For #!/bin/bash: You need to verify if bash is installed and located at /bin/bash.

      which bash
      # Expected output (if installed): /usr/bin/bash or /bin/bash
      

      If which bash returns nothing, bash is not installed or not in the PATH.

  3. Install Bash (if required and not present): If your script absolutely requires bash syntax and features, install it using Alpine's package manager, apk.

    apk add bash
    

    Alpine Linux prioritizes minimalism. Installing bash adds to your image size, which can be significant in containerized environments. Only install bash if strictly necessary. Many simple scripts can be rewritten for POSIX sh compliance.

  4. Adjust the shebang or script logic:

    • Option A (Recommended for simplicity): If your script doesn't use advanced bash-specific features, consider changing the shebang to #!/bin/sh.

      sed -i 's|#!/bin/bash|#!/bin/sh|g' my-script.sh
      

      BusyBox ash (Alpine's /bin/sh) is a lightweight shell with fewer features than GNU bash. Scripts relying on bash arrays, advanced string manipulation, process substitution, or specific parameter expansions might break if switched to #!/bin/sh. Test thoroughly!

    • Option B (If bash is essential): Ensure bash is installed and the shebang correctly points to its location (usually #!/usr/bin/bash or #!/bin/bash). If which bash shows /usr/bin/bash, update your script's shebang accordingly if it's currently #!/bin/bash.

  5. Attempt execution: Try running your script again.

    ./my-script.sh
    

3. Check Filesystem Mount Options

If you're still facing "Permission denied," especially in Docker containers or on specific mounted volumes, the noexec mount option is a prime suspect.

  1. Inspect mount options: Use the mount command to list all mounted filesystems and their options. Look for the directory where your script is located.

    mount
    

    Examine the output for your relevant mount point. A line like /dev/sda1 on /var/www type ext4 (rw,noexec,relatime) indicates noexec is active.

  2. Resolution for Docker/Container Environments: If you're using Docker, the noexec option can come from the host's mount options for bind mounts, or specific Docker volume configurations. To explicitly allow execution, add o=exec to your bind mount definition:

    docker run -v /host/path/to/scripts:/container/app/scripts:o=exec my-alpine-image /container/app/scripts/my-script.sh
    

    For named volumes, you might need to check how the volume driver configures its mounts.

  3. Resolution for VMs/Bare Metal: If your script is on a filesystem mounted with noexec on a VM or bare-metal server, you'll need to modify your /etc/fstab file and remount the filesystem.

    1. Backup /etc/fstab:

      cp /etc/fstab /etc/fstab.bak
      
    2. Edit /etc/fstab: Open /etc/fstab with a text editor (e.g., vi /etc/fstab or nano /etc/fstab). Locate the line corresponding to the filesystem in question. Change noexec to exec in the options field.

      Example before:

      /dev/sda1 /var/www ext4 defaults,noexec 0 2
      

      Example after:

      /dev/sda1 /var/www ext4 defaults,exec 0 2
      
    3. Remount the filesystem:

      sudo mount -o remount /var/www # Replace /var/www with your mount point
      

      Modifying /etc/fstab incorrectly can prevent your system from booting. Always back up the file and double-check your changes before rebooting. If possible, test with mount -o remount first.

  4. Attempt execution: Test your script again.

    ./my-script.sh
    

4. Resolve Windows Line Endings

While typically causing "command not found" errors for the shebang, CRLF line endings can sometimes contribute to general execution failures if the system incorrectly parses the script header.

  1. Check for CRLF characters: You can use cat -v to reveal non-printable characters or file command for basic detection.

    cat -v my-script.sh | head -n 1
    # Expected output for CRLF: #!/bin/bash^M
    file my-script.sh
    # Expected output for CRLF: my-script.sh: a /bin/bash script, ASCII text executable, with CRLF line terminators
    
  2. Install dos2unix: If dos2unix is not already installed on your Alpine system, install it:

    apk add dos2unix
    
  3. Convert the script: Use dos2unix to convert the file in place.

    dos2unix my-script.sh
    

    This will convert all CRLF endings to LF.

  4. Attempt execution: Try running your script again.

    ./my-script.sh
    

By systematically working through these steps, you should be able to diagnose and resolve the "Permission denied" error for your shell scripts on Alpine Linux, ensuring your applications and automation run smoothly in this lightweight environment.

👨‍💻

Johnathon Wheeler

Senior Systems Architect & DevOps Engineer • Austin, TX

Connect on LinkedIn

Johnathon has over 16 years of hands-on experience designing, debugging, and scaling Linux web hosting stacks, container clusters, and high-availability database architectures. Every guide on ButItWorkedLocal is independently tested against Debian 12, Ubuntu 24.04/22.04 LTS, Rocky Linux, and Docker environments to guarantee reproducibility in production.

🛡️

Our Production Verification Guarantee

Encountering a bug not covered here or running a non-standard kernel configuration? Our solutions are continually refined against real production incidents. Submit an environment trace for our editorial team to replicate.