How to Fix "Permission Denied" in Linux

The infamous terminal message bash: ./script.sh: Permission denied or Nginx 403 Forbidden: Permission denied is perhaps the most ubiquitous stumbling block in Linux administration. When confronted with this error, many developers impulsively reach for the sledgehammer solution: running sudo chmod -R 777. While brute-forcing chmod 777 might temporarily bypass the error, it destroys security boundaries, introduces severe vulnerabilities, and frequently fails to resolve the underlying issue. A methodical troubleshooting workflow reveals that "Permission Denied" errors stem from four distinct architectural layers: ownership conflicts, missing execute bits on binaries, lack of traverse access on parent directories, or mandatory security policies like SELinux.

Advertisement

Check Ownership First

Before modifying any numerical permission bits, you must always verify user and group ownership. In the Linux security model, the kernel evaluates permissions strictly based on the current process identity compared to the file owner UID and group GID. If your process does not belong to the proper account, no amount of permission adjustment short of full world access will help.

# Check ownership and permissions using ls -l
$ ls -l deploy.sh
-rwxr-xr-- 1 root root 2048 Jan 15 10:00 deploy.sh

# Who are you running as?
$ whoami
developer
$ id
uid=1000(developer) gid=1000(developer) groups=1000(developer),27(sudo)

In the scenario above, deploy.sh is owned by root:root. Because user developer is neither the owner nor in the root group, the kernel evaluates access solely under the "others" class, which has only read permission (r--). Attempting to run ./deploy.sh triggers an immediate "Permission Denied" because others lack execute rights. Rather than making the file world-writable, the correct solution is transferring ownership using chown:

# Correct fix: Transfer ownership to the active developer
sudo chown developer:developer deploy.sh

# Now the user permissions (rwx) apply directly!
./deploy.sh

Missing Execute Bit

When executing compiled binaries, Python scripts, or Bash automation, Linux requires an explicit execute bit on the file inode. Unlike operating systems that infer execution rights solely from file extensions like .exe or .sh, Linux completely ignores the extension. If you download a new script or clone a repository, the file often arrives with default file permissions like chmod 644 (read and write, but no execution).

# Inspect the permissions of your script
$ ls -l backup.sh
-rw-r--r-- 1 developer developer 1024 Jan 15 10:10 backup.sh

$ ./backup.sh
bash: ./backup.sh: Permission denied

# Solution: Add execute permission for the file owner
$ chmod +x backup.sh

# The file now shows rwxr-xr-x (chmod 755)
$ ls -l backup.sh
-rwxr-xr-x 1 developer developer 1024 Jan 15 10:10 backup.sh
$ ./backup.sh

For interpreted scripts like Python or Bash, the user executing the file also requires read permission in addition to execute permission. If you configure a script as chmod 100 or chmod 111 (execute only), compiled ELF binaries will run, but interpreted scripts will fail with "Permission Denied" because the interpreter cannot open the file to read source code. Standard executable scripts should always be configured with chmod 755.

Directory Traverse Permission

A frequent mystery occurs when a file has seemingly open permissions—such as chmod 644 or even chmod 777—yet the web server or application continues to report "Permission Denied". In 90% of these cases, the root cause lies not within the file itself, but in one of the parent directories leading to that file.

# Scenario: Web server user www-data cannot read index.html
$ ls -l /home/developer/site/index.html
-rw-r--r-- 1 developer developer 512 Jan 15 10:15 /home/developer/site/index.html

# But check the parent home directory permissions!
$ ls -ld /home/developer
drwx------ 15 developer developer 4096 Jan 15 09:00 /home/developer

Notice that /home/developer is configured as chmod 700 (private to the developer). In Linux, to access any file inside a directory tree, a process must possess the execute (x) bit on every single directory along the entire absolute path. Because /home/developer denies execute access to others, the web server process www-data cannot traverse past /home/developer to reach /home/developer/site/index.html, triggering an immediate 403 Forbidden error. To resolve this, web document roots should reside in standard paths like /var/www/html where parent directories maintain proper chmod 755 traverse rights.

sudo vs chmod

Another major source of confusion is understanding when to use sudo versus when to use chmod. A common failure pattern involves shell redirection:

# This command FAILS with "Permission denied":
$ sudo echo "nameserver 1.1.1.1" > /etc/resolv.conf
bash: /etc/resolv.conf: Permission denied

# Why? Because your unprivileged shell opens the redirect (>)
# BEFORE sudo executes the echo command!

# The correct solution: Use tee with sudo
$ echo "nameserver 1.1.1.1" | sudo tee /etc/resolv.conf

Do not change system file permissions simply to avoid using sudo. For example, never change system configuration files in /etc to chmod 666 so your user account can edit them without elevated rights. Always preserve system defaults and invoke administrative tools appropriately.

SELinux/AppArmor Note

If you have verified that whoami matches file ownership, the execute bits are set, parent directories have chmod 755 traverse permissions, and you still receive "Permission Denied", your system is likely governed by a Mandatory Access Control (MAC) security layer such as SELinux (standard on RHEL, AlmaLinux, Rocky, and Fedora) or AppArmor (standard on Ubuntu and Debian).

# Check for SELinux access violations
$ getenforce
Enforcing

# View extended security context with -Z
$ ls -Z /var/www/html/index.html
unconfined_u:object_r:user_home_t:s0 /var/www/html/index.html

# Fix web server security context using restorecon
$ sudo restorecon -vR /var/www/html
Relabeled /var/www/html/index.html from user_home_t to httpd_sys_content_t

If an asset was copied from a home directory into /var/www/html, it may retain the user_home_t security context. The web daemon is prevented by SELinux from reading files labeled as home content, generating a "Permission Denied" error regardless of standard Unix permissions. Running restorecon -vR immediately aligns SELinux labels with security policy, clearing the block cleanly and safely.

Advertisement

Frequently Asked Questions

Why do I get 'Permission Denied' even when running as sudo?

Even with sudo, shell redirects like 'sudo echo text > /etc/file' fail because the redirection operator is executed by your unprivileged shell before sudo runs. Use 'echo text | sudo tee /etc/file' instead.

Can I get 'Permission Denied' if the file has 777 permissions?

Yes. If any parent directory leading to the file lacks execute permission (the traverse bit), your process cannot reach the file, triggering 'Permission Denied' regardless of the file's individual mode.

How does SELinux cause permission denied errors?

SELinux operates as a Mandatory Access Control system on top of standard DAC permissions. Even if chmod and chown allow access, mismatched SELinux security contexts will block system calls.

Advertisement