Linux & OS Beginner

Linux Shell Script ‘Permission Denied’ Error on CentOS Stream / Rocky Linux (RHEL-based)

Troubleshoot 'Permission Denied' when running bash scripts on CentOS Stream or Rocky Linux. Learn to fix execute permissions, shebang, and SELinux issues.

👨‍💻
Senior Systems Architect • Verified in Staging Labs

Troubleshoot 'Permission Denied' when running bash scripts on CentOS Stream or Rocky Linux. Learn to fix execute permissions, shebang, and SELinux issues.

Introduction

As an experienced Systems Administrator, one of the most common and often frustrating issues newcomers and even seasoned professionals encounter is the dreaded "Permission denied" error when attempting to execute a shell script. While seemingly simple, this error can stem from various underlying causes, especially on security-hardened RHEL-based distributions like CentOS Stream and Rocky Linux. This guide provides a comprehensive, technical walkthrough to diagnose and resolve this issue, focusing on crucial aspects like file permissions, shebang configuration, and critically, SELinux contexts.

Symptom & Error Signature

When you attempt to run a shell script from your terminal, you will typically see an error message similar to one of the following:

-bash: ./my_script.sh: Permission denied

or if the script is located in a directory not in your PATH:

-bash: /opt/scripts/my_script.sh: Permission denied

You might also see the Permission denied error even when attempting to execute the script directly via bash:

bash my_script.sh
bash: my_script.sh: Permission denied

This last symptom often points to issues beyond simple execute permissions, such as SELinux restrictions or filesystem mount options.

Root Cause Analysis

The "Permission denied" error indicates that the operating system is preventing the current user from performing a requested operation – in this case, executing a script. The root causes can be multifaceted:

  1. Lack of Execute Permission (Primary Cause): The most common reason. Linux file permissions are granular, requiring explicit "execute" (x) permission for the user, group, or others to run a file as a program. Without this bit set, the kernel will refuse to execute it.
  2. Incorrect Shebang Line: While not directly a "permission denied" error, if the shebang (#!) line is missing, incorrect, or points to a non-existent interpreter, the system might not know how to execute the file, or it might try to execute it in an unexpected way, leading to permission issues or "command not found". If you explicitly run bash script.sh, this is less of an issue, but if you run ./script.sh, the shebang is crucial.
  3. SELinux Context Restrictions: Highly prevalent on RHEL-based systems (CentOS Stream, Rocky Linux, AlmaLinux). Even with correct file permissions, SELinux (Security-Enhanced Linux) might prevent a process from executing a script if its security context does not permit it. For example, a script intended for web execution (httpd_sys_script_exec_t) might fail if it's placed in a user's home directory (user_home_t) and a web server tries to run it.
  4. Filesystem Mount Options: The filesystem where the script resides might be mounted with the noexec option, explicitly preventing any file on that filesystem from being executed. This is common for /tmp or /var/tmp for security reasons.
  5. Parent Directory Permissions: While the script itself might have correct permissions, if the user doesn't have "execute" (traverse) permission on one or more of its parent directories, they won't be able to access the script's path, resulting in a "Permission denied" error.

Step-by-Step Resolution

Follow these steps systematically to diagnose and resolve the "Permission denied" error.

#### 1. Verify Script Path and Existence

First, ensure the script actually exists and you're in the correct directory or providing the correct path.

# Check current directory
pwd

# List files to confirm script existence
ls -F

# Verify full path, if applicable
ls -l /path/to/your/script.sh

#### 2. Grant Execute Permissions (The Primary Fix)

This is the most common fix. Use the chmod command to add execute permissions.

  1. Check current permissions:

    ls -l my_script.sh
    # Example output: -rw-r--r--. 1 user group 1234 Aug 30 10:00 my_script.sh
    # Notice the absence of 'x' in the permission string.
    
  2. Add execute permission for the owner:

    chmod u+x my_script.sh
    # Or for owner, group, and others (a common choice for scripts, like 755)
    # chmod 755 my_script.sh
    

    chmod 755 sets permissions to rwxr-xr-x, allowing the owner to read, write, and execute, and group/others to read and execute. Avoid chmod 777 (rwxrwxrwx) unless absolutely necessary, as it grants full control to everyone and is a significant security risk. For most scripts, 755 is appropriate.

  3. Verify new permissions:

    ls -l my_script.sh
    # Expected output: -rwxr-xr-x. 1 user group 1234 Aug 30 10:00 my_script.sh
    # The 'x' bits should now be present.
    
  4. Attempt to run the script:

    ./my_script.sh
    

    If you are still getting Permission denied, proceed to the next steps.

#### 3. Check the Shebang Line

The shebang line (#!) tells the system which interpreter to use for the script.

  1. Examine the first line of your script:

    head -1 my_script.sh
    # Expected: #!/bin/bash
    # Or:       #!/usr/bin/env bash
    
  2. Ensure the interpreter exists: The path specified after #! must be valid.

    which bash
    # Expected: /usr/bin/bash (or /bin/bash, which is often a symlink to /usr/bin/bash)
    

    If which bash returns a different path, or no path, adjust your shebang line accordingly. #!/usr/bin/env bash is often more portable as it finds bash in the system's PATH.

#### 4. Address SELinux Context

SELinux is a critical security enhancement on RHEL-based systems. It can block script execution even with correct file permissions.

  1. Check the SELinux context of the script:

    ls -Z my_script.sh
    # Example output: -rwxr-xr-x. user group unconfined_u:object_r:user_home_t:s0 my_script.sh
    

    Pay attention to the type field (e.g., user_home_t, httpd_sys_script_exec_t). If the context is user_home_t but the script is being executed by a service (like Apache/Nginx via PHP-FPM, or a systemd service), SELinux might be blocking it.

  2. Restore default SELinux context: The restorecon command applies the default SELinux context for files based on system policy.

    sudo restorecon -v my_script.sh
    # If the script is in a standard system path, this will often fix it.
    
  3. Manually change SELinux context (if restorecon doesn't apply the correct one): If the script is, for example, a web application helper script, it might need httpd_sys_script_exec_t.

    sudo chcon -v --type=httpd_sys_script_exec_t my_script.sh
    # Replace 'httpd_sys_script_exec_t' with the appropriate context for your use case.
    # Common contexts:
    #   httpd_sys_script_exec_t : For scripts executed by web servers.
    #   bin_t                 : For executables in /usr/bin, /usr/local/bin.
    #   usr_t                 : For general user-level scripts/executables.
    

    chcon changes are temporary and may be reset by restorecon or a filesystem relabel. For persistent changes, you should update the SELinux policy rules or use semanage fcontext. For one-off scripts, chcon is often sufficient.

  4. Check SELinux audit logs for denials: If the script is still failing, SELinux might be silently blocking it. Check the audit logs:

    sudo ausearch -c bash -m AVC -ts recent
    # Or for a broader search:
    sudo journalctl -t audit -f | grep "AVC"
    

    Look for messages containing AVC denied related to your script or the interpreter (bash). This will provide detailed information about what SELinux blocked and why.

  5. Temporarily disable SELinux (for testing ONLY):

    Disabling SELinux reduces your system's security. Only do this temporarily for testing purposes in a controlled environment. Re-enable it immediately after testing.

    sudo setenforce 0  # Set SELinux to Permissive mode
    

    Try running your script now. If it works, SELinux was indeed the cause.

    sudo setenforce 1  # Re-enable SELinux (Enforcing mode)
    

    If SELinux was the culprit, you need to either adjust the script's SELinux context permanently (using chcon or semanage fcontext) or write a custom SELinux policy rule.

#### 5. Review Filesystem Mount Options

A less common but important cause is the noexec mount option.

  1. Check mount options for the script's filesystem:

    findmnt -n -o OPTIONS --target /path/to/your/script.sh
    # Or more generally:
    mount | grep $(df /path/to/your/script.sh | awk 'NR==2 {print $1}')
    

    Look for noexec in the output. If it's present, the system explicitly forbids execution from that filesystem.

  2. Remount with exec (temporary):

    # Identify the mount point (e.g., /tmp)
    df /path/to/your/script.sh
    # Then remount:
    sudo mount -o remount,exec /tmp
    
  3. Permanently change in /etc/fstab: If noexec is present and you need to execute scripts from that location permanently, you must edit /etc/fstab and change noexec to exec for that specific mount point. Then reboot or remount the filesystem.

    Modifying /etc/fstab incorrectly can make your system unbootable. Always back up the file before editing: sudo cp /etc/fstab /etc/fstab.bak.

#### 6. Verify Parent Directory Permissions

Ensure that all parent directories leading to your script allow "traverse" (execute) permission for the user trying to run the script.

  1. Check permissions of parent directories:

    namei -l /path/to/your/script.sh
    # This command shows permissions for each component of the path.
    # Example output:
    # dr-xr-xr-x root root /
    # drwxr-xr-x root root usr
    # drwxr-xr-x root root local
    # drwxr-xr-x root root bin
    # -rwxr-xr-x user user my_script.sh
    

    For directories, the x bit means you can cd into them and access their contents. If any directory in the path lacks the x bit for the relevant user/group, access will be denied.

  2. Adjust directory permissions (if necessary):

    sudo chmod a+x /path/to/parent_directory
    # Or more specific:
    sudo chmod u+x /path/to/parent_directory # For user
    sudo chmod g+x /path/to/parent_directory # For group
    

    Make sure not to over-permission directories, especially /.

👨‍💻

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.