Introduction

File permissions are the foundation of security on any Linux system. Every file and directory on disk carries metadata that determines who can read, write, or execute it. A misconfigured permission on a single file can expose sensitive data, allow unauthorized code execution, or break an entire application.

This guide walks you through the complete Linux permission model — from the basic rwx bits through special permissions like setuid and the sticky bit, all the way to POSIX Access Control Lists (ACLs) for fine-grained, multi-user access control. Whether you are hardening a production server, configuring a shared development environment, or troubleshooting a “Permission denied” error, this article gives you the knowledge and commands you need.

Prerequisites

Before you begin, make sure you have:

  • A Linux system (Ubuntu, Debian, CentOS, Fedora, Arch, or any distribution).
  • Terminal access with a regular user account.
  • sudo privileges for commands that require root access.
  • The acl package installed (for ACL commands). On most distributions it is included by default. If not:
# Debian/Ubuntu
sudo apt install acl

# CentOS/RHEL/Fedora
sudo dnf install acl
  • A filesystem that supports ACLs. ext4, XFS, and Btrfs all support ACLs natively.

Understanding Linux Permissions

The ls -l Output Explained

The ls -l command is your primary tool for inspecting permissions. Each line of output contains a wealth of information:

ls -l /etc/passwd
-rw-r--r-- 1 root root 2847 Feb 10 08:30 /etc/passwd

Breaking this down field by field:

FieldValueMeaning
File type-Regular file (d = directory, l = symlink, b = block device, c = char device)
Owner permissionsrw-Owner can read and write, but not execute
Group permissionsr--Group members can read only
Others permissionsr--All other users can read only
Hard link count1Number of hard links to this inode
OwnerrootThe user who owns the file
GrouprootThe group assigned to the file
Size2847File size in bytes
TimestampFeb 10 08:30Last modification time
Name/etc/passwdFile path

The Three Permission Categories

Linux divides access into three categories:

  • Owner (u) — The user who owns the file. By default, the user who creates a file becomes its owner.
  • Group (g) — The group assigned to the file. All members of this group share the group permissions.
  • Others (o) — Everyone else on the system who is not the owner and not a member of the file’s group.

The Three Permission Bits

Each category can have three types of access:

PermissionSymbolOctalEffect on FilesEffect on Directories
Readr4View file contentsList directory contents
Writew2Modify file contentsCreate, delete, rename files inside
Executex1Run as a programEnter (cd into) the directory

A critical distinction: write permission on a directory allows creating and deleting files inside it, regardless of the permissions on those individual files. This is why the sticky bit exists (covered later).

Directories vs. Files

Permission behavior differs between files and directories. Understanding this distinction prevents common mistakes:

# A directory needs execute permission for users to cd into it
ls -ld /var/log
drwxr-xr-x 15 root syslog 4096 Feb 12 00:00 /var/log

# A file needs execute permission only if it is a script or binary
ls -l /usr/bin/ls
-rwxr-xr-x 1 root root 142144 Sep  5  2025 /usr/bin/ls

A directory with read but no execute permission produces an unusual result — users can list filenames inside it, but cannot access file metadata (size, permissions) or open any files within it.

Permission Notation

Symbolic Notation

Symbolic notation uses letters and operators to describe permissions. It is the format displayed by ls -l and accepted by chmod in symbolic mode.

The format is: [ugoa][+-=][rwxXstugo]

  • Who: u (owner), g (group), o (others), a (all)
  • Operator: + (add), - (remove), = (set exactly)
  • Permission: r (read), w (write), x (execute), X (execute only if directory or already executable), s (setuid/setgid), t (sticky)

Examples:

# Add execute permission for the owner
chmod u+x script.sh

# Remove write permission from group and others
chmod go-w config.yml

# Set exact permissions: owner rwx, group rx, others rx
chmod u=rwx,g=rx,o=rx app.bin

# Add read permission for all categories at once
chmod a+r document.txt

Octal Notation

Octal notation represents each permission set as a single digit (0–7). Each digit is the sum of its permission values:

OctalBinaryPermissions
0000--- (none)
1001--x (execute only)
2010-w- (write only)
3011-wx (write + execute)
4100r-- (read only)
5101r-x (read + execute)
6110rw- (read + write)
7111rwx (all)

The three-digit octal number maps to owner, group, and others in order:

# 755 = owner rwx (7), group r-x (5), others r-x (5)
chmod 755 /var/www/html

# 644 = owner rw- (6), group r-- (4), others r-- (4)
chmod 644 /var/www/html/index.html

# 600 = owner rw- (6), group --- (0), others --- (0)
chmod 600 ~/.ssh/id_rsa

Quick Reference Table

Use CaseSymbolicOctalPermission String
Private SSH keyu=rw,go=600-rw-------
Standard fileu=rw,go=r644-rw-r--r--
Executable scriptu=rwx,go=rx755-rwxr-xr-x
Shared directoryu=rwx,g=rwx,o=rx775drwxrwxr-x
Fully privateu=rwx,go=700-rwx------
Config with secretsu=rw,g=r,o=640-rw-r-----

Changing Permissions with chmod

The chmod (change mode) command modifies the permission bits on files and directories.

Basic Usage

# Symbolic mode — add execute for owner
chmod u+x deploy.sh

# Octal mode — set to 755
chmod 755 deploy.sh

# Check the result
ls -l deploy.sh
-rwxr-xr-x 1 admin admin 2048 Feb 12 10:00 deploy.sh

Recursive Permission Changes

Use the -R flag to apply permissions recursively to all files and directories inside a path:

# Set directories to 755 and files to 644 recursively
# First, set everything to 755
chmod -R 755 /var/www/html

# Then, set only regular files to 644
find /var/www/html -type f -exec chmod 644 {} \;

The two-step approach is important. Applying chmod 644 recursively would remove execute permission from directories, making them inaccessible. Always handle files and directories separately:

# Set all directories to 755
find /var/www/html -type d -exec chmod 755 {} \;

# Set all files to 644
find /var/www/html -type f -exec chmod 644 {} \;

The Capital X Permission

The X (capital) permission adds execute only if the target is a directory or already has execute permission. This is a safe way to apply permissions recursively without manually separating files and directories:

# Remove execute from files but keep it on directories
chmod -R u=rwX,go=rX /var/www/html

Preserving Root Directory Permissions

When applying recursive changes, be careful not to alter the parent directory itself if it has intentionally different permissions:

# Apply to contents only, not the directory itself
find /opt/app -mindepth 1 -type d -exec chmod 750 {} \;
find /opt/app -mindepth 1 -type f -exec chmod 640 {} \;

Changing Ownership with chown and chgrp

chown — Change Owner and Group

The chown command changes the owner and optionally the group of a file or directory. Only root can change file ownership.

# Change the owner to 'webadmin'
sudo chown webadmin index.html

# Change owner and group simultaneously
sudo chown webadmin:www-data index.html

# Change only the group (note the leading colon)
sudo chown :www-data index.html

# Recursive ownership change
sudo chown -R webadmin:www-data /var/www/html

chgrp — Change Group Only

The chgrp command changes only the group. A regular user can use chgrp if they are a member of the target group:

# Change the group to 'developers'
chgrp developers project-plan.md

# Recursive group change
sudo chgrp -R developers /opt/project

Practical Example: Transfer Ownership

When a team member leaves and you need to reassign their files:

# Find all files owned by the departing user
find /opt/projects -user olduser -type f

# Reassign ownership
sudo find /opt/projects -user olduser -exec chown newuser:devteam {} \;

Understanding umask

The umask (user file-creation mask) determines the default permissions for newly created files and directories. It specifies which permission bits to remove from the system defaults.

The system defaults are:

  • Files: 666 (rw-rw-rw-)
  • Directories: 777 (rwxrwxrwx)

The umask is subtracted from these defaults:

# Check the current umask
umask
0022

# With umask 022:
# New files:       666 - 022 = 644 (rw-r--r--)
# New directories:  777 - 022 = 755 (rwxr-xr-x)

Setting umask

# Set umask for the current session
umask 027

# With umask 027:
# New files:       666 - 027 = 640 (rw-r-----)
# New directories:  777 - 027 = 750 (rwxr-x---)

Making umask Persistent

To set the umask permanently, add it to your shell configuration:

# For bash — add to ~/.bashrc or ~/.profile
echo "umask 027" >> ~/.bashrc

# For system-wide default — edit /etc/login.defs
sudo grep -n UMASK /etc/login.defs
# UMASK   022

On systems using pam_umask, the system-wide umask is configured in /etc/login.defs. Per-user overrides go in ~/.bashrc or ~/.profile.

Common umask Values

umaskFile DefaultDirectory DefaultUse Case
022644755Standard — readable by everyone
027640750Moderate — group can read, others blocked
077600700Restrictive — owner only
002664775Collaborative — group can write

Special Permissions

Beyond the basic rwx bits, Linux supports three special permission bits: setuid, setgid, and the sticky bit. These are represented as a fourth octal digit prepended to the standard three.

setuid (Set User ID) — Octal 4

When set on an executable file, setuid causes the program to run with the privileges of the file owner, not the calling user.

# Classic example: the passwd command runs as root
ls -l /usr/bin/passwd
-rwsr-xr-x 1 root root 68208 Feb  6  2025 /usr/bin/passwd

The s in the owner’s execute position indicates setuid is active. This allows regular users to change their own passwords by writing to /etc/shadow, a file only root can modify.

# Set the setuid bit
sudo chmod u+s /usr/local/bin/custom-tool

# Using octal (4 = setuid)
sudo chmod 4755 /usr/local/bin/custom-tool

Security warning: setuid on shell scripts is ignored by the kernel on modern Linux systems for security reasons. setuid is only effective on compiled binaries. Applying setuid carelessly is a major security risk — it can grant root access to any user.

setgid (Set Group ID) — Octal 2

setgid behaves differently depending on whether it is applied to a file or a directory.

On executable files: The program runs with the privileges of the file’s group, not the user’s primary group.

# Example: the wall command runs with group 'tty'
ls -l /usr/bin/wall
-rwxr-sr-x 1 root tty 30800 Sep  5  2025 /usr/bin/wall

On directories (much more common): New files and subdirectories created inside inherit the group of the parent directory, instead of the user’s primary group. This is essential for shared directories.

# Create a shared project directory with setgid
sudo mkdir /opt/shared-project
sudo chown root:developers /opt/shared-project
sudo chmod 2775 /opt/shared-project

# Verify — note the 's' in the group execute position
ls -ld /opt/shared-project
drwxrwsr-x 2 root developers 4096 Feb 12 10:00 /opt/shared-project

# Any file created inside inherits the 'developers' group
touch /opt/shared-project/newfile.txt
ls -l /opt/shared-project/newfile.txt
-rw-rw-r-- 1 alice developers 0 Feb 12 10:01 newfile.txt

Sticky Bit — Octal 1

The sticky bit on a directory restricts file deletion. Only the file owner, the directory owner, or root can delete or rename files inside the directory.

# /tmp is the classic sticky-bit directory
ls -ld /tmp
drwxrwxrwt 18 root root 4096 Feb 12 10:00 /tmp

The t in the others’ execute position indicates the sticky bit is active.

# Set the sticky bit on a shared directory
sudo chmod +t /opt/shared-uploads

# Or using octal
sudo chmod 1777 /opt/shared-uploads

Special Permissions Summary

PermissionOctalOn FilesOn Directories
setuid4000Runs as file ownerNo standard effect
setgid2000Runs as file groupNew files inherit directory group
sticky1000No modern effectOnly owner can delete their files

Identifying Special Permissions in ls -l

SymbolPositionMeaning
s (lowercase)Owner executesetuid + execute
S (uppercase)Owner executesetuid without execute (unusual)
s (lowercase)Group executesetgid + execute
S (uppercase)Group executesetgid without execute
t (lowercase)Others executesticky + execute
T (uppercase)Others executesticky without execute (unusual)

An uppercase letter (S or T) indicates the special bit is set but the underlying execute bit is not — this usually indicates a misconfiguration.

POSIX Access Control Lists (ACLs)

Why ACLs?

Standard Linux permissions support exactly one owner and one group per file. This creates problems in real-world scenarios:

  • A web directory needs to be writable by the webadmin user, readable by the www-data group, and also writable by the deployer user for automated deployments.
  • A shared project needs different access levels for different teams.
  • Log files need to be readable by monitoring systems without adding them to the file’s group.

ACLs solve this limitation by allowing you to attach multiple named user and group entries to a file’s permission metadata.

Checking ACL Support

Verify that your filesystem supports ACLs:

# Check mount options
mount | grep ' / '
/dev/sda1 on / type ext4 (rw,relatime,errors=remount-ro)

# For ext4, ACLs are enabled by default
# For older systems, check /etc/fstab for the 'acl' mount option
grep acl /etc/fstab

Reading ACLs with getfacl

The getfacl command displays the full ACL of a file or directory:

# Standard file without extra ACLs
getfacl /var/www/html/index.html
# file: var/www/html/index.html
# owner: webadmin
# group: www-data
user::rw-
group::r--
other::r--

This output mirrors the standard ls -l permissions. The user:: entry (without a name) represents the owner, group:: represents the owning group, and other:: represents everyone else.

Setting ACLs with setfacl

The setfacl command adds, modifies, or removes ACL entries.

# Grant read and write access to a specific user
setfacl -m u:deployer:rw /var/www/html/index.html

# Grant read access to a specific group
setfacl -m g:monitoring:r /var/log/app/application.log

# Verify the ACL
getfacl /var/www/html/index.html
# file: var/www/html/index.html
# owner: webadmin
# group: www-data
user::rw-
user:deployer:rw-
group::r--
group:monitoring:r--
mask::rw-
other::r--

Notice two new elements:

  • user:deployer:rw- — The named user entry.
  • mask::rw- — The ACL mask (explained below).

Common setfacl Operations

# Add permission for a named user
setfacl -m u:alice:rwx /opt/project

# Add permission for a named group
setfacl -m g:qa-team:rx /opt/project

# Set multiple entries at once
setfacl -m u:alice:rwx,g:qa-team:rx,g:devops:rw /opt/project

# Remove a specific ACL entry
setfacl -x u:alice /opt/project

# Remove all ACL entries (revert to standard permissions)
setfacl -b /opt/project

# Apply ACLs recursively
setfacl -R -m g:developers:rwX /opt/project

Default ACLs for Directories

Default ACLs are set on directories and automatically apply to new files and subdirectories created inside them. They do not affect the directory itself — they define what ACL entries new children inherit.

# Set a default ACL so new files inside are readable by 'monitoring'
setfacl -d -m g:monitoring:r /var/log/app

# Set default ACLs for a shared project directory
setfacl -d -m u::rwx,g::rwx,o::rx /opt/shared-project

# Verify default ACLs
getfacl /opt/shared-project
# file: opt/shared-project
# owner: root
# group: developers
user::rwx
group::rwx
other::r-x
default:user::rwx
default:group::rwx
default:group:monitoring:r--
default:other::r-x
default:mask::rwx

Lines prefixed with default: show the inheritable ACL entries.

The ACL Mask

The ACL mask defines the maximum effective permissions for all named user entries, the owning group, and all named group entries. It acts as an upper-bound filter.

# Set the mask to read-only
setfacl -m m::r /opt/project/sensitive.conf

# Now even if alice has rwx in her ACL entry, the effective permission is r only
getfacl /opt/project/sensitive.conf
# file: opt/project/sensitive.conf
# owner: admin
# group: devteam
user::rw-
user:alice:rwx          #effective:r--
group::rw-              #effective:r--
mask::r--
other::---

The #effective: comment shows the actual permissions after the mask is applied. The mask does not affect the owner entry (user::) or the others entry (other::).

Important: When you use chmod on a file that has ACLs, the group permission field modifies the mask, not the owning group’s ACL entry. This is a common source of confusion:

# This changes the ACL mask, not the group permission
chmod g-w /opt/project/sensitive.conf

Detecting ACL-Enabled Files

Files with ACLs show a + sign at the end of the permission string in ls -l:

ls -l /opt/project/
-rw-r--r--+ 1 admin devteam 1024 Feb 12 10:00 sensitive.conf

The + indicates that extended ACL entries exist beyond the base permissions.

Backing Up and Restoring ACLs

# Save ACLs for an entire directory tree
getfacl -R /opt/project > project-acls.txt

# Restore ACLs from a backup
setfacl --restore=project-acls.txt

Web Server Permissions

Proper file permissions are critical for web server security. The web server process runs as a specific user — www-data on Debian/Ubuntu (Apache and Nginx) or nginx on CentOS/RHEL.

Identifying the Web Server User

# Apache on Debian/Ubuntu
grep -i 'user\|group' /etc/apache2/envvars
export APACHE_RUN_USER=www-data
export APACHE_RUN_GROUP=www-data

# Nginx
grep -i 'user' /etc/nginx/nginx.conf
user www-data;

# Check which user the web server process is running as
ps aux | grep -E 'apache|nginx|httpd' | head -5

Standard Web Directory Permissions

# Set ownership — your user as owner, web server as group
sudo chown -R webadmin:www-data /var/www/html

# Directories: 755 (owner rwx, group and others r-x)
sudo find /var/www/html -type d -exec chmod 755 {} \;

# Files: 644 (owner rw, group and others r)
sudo find /var/www/html -type f -exec chmod 644 {} \;

WordPress Permissions Example

WordPress requires specific permissions for different directories:

# Set base ownership
sudo chown -R www-data:www-data /var/www/wordpress

# Standard directory permissions
sudo find /var/www/wordpress -type d -exec chmod 755 {} \;

# Standard file permissions
sudo find /var/www/wordpress -type f -exec chmod 644 {} \;

# wp-config.php should be more restrictive
sudo chmod 640 /var/www/wordpress/wp-config.php

# wp-content needs write access for uploads and plugin updates
sudo chmod 775 /var/www/wordpress/wp-content
sudo chmod -R 775 /var/www/wordpress/wp-content/uploads
sudo chmod -R 775 /var/www/wordpress/wp-content/plugins
sudo chmod -R 775 /var/www/wordpress/wp-content/themes

# Prevent .htaccess modification unless explicitly needed
sudo chmod 644 /var/www/wordpress/.htaccess

Using ACLs for Web Deployment

When a deployment user (separate from the web server user) needs write access:

# Base ownership goes to the web server
sudo chown -R www-data:www-data /var/www/app

# Grant the deployment user write access via ACL
sudo setfacl -R -m u:deployer:rwX /var/www/app
sudo setfacl -R -d -m u:deployer:rwX /var/www/app

# Grant the developer group read access for debugging
sudo setfacl -R -m g:developers:rX /var/www/app
sudo setfacl -R -d -m g:developers:rX /var/www/app

Shared Directory Setup

A common requirement in multi-user environments is a directory where several users or teams can collaborate on files. Here is a complete setup:

# Create the shared directory
sudo mkdir -p /opt/team-project

# Create the group and add users
sudo groupadd project-team
sudo usermod -aG project-team alice
sudo usermod -aG project-team bob
sudo usermod -aG project-team carol

# Set ownership and setgid
sudo chown root:project-team /opt/team-project
sudo chmod 2775 /opt/team-project

# Ensure new files inherit the group (setgid handles this)
# Set a collaborative umask for team members
# Each user should add to their ~/.bashrc:
# umask 002

# Add the sticky bit to prevent accidental deletion of others' files
sudo chmod +t /opt/team-project

# Final permissions: drwxrwsr-t
ls -ld /opt/team-project
drwxrwsr-t 2 root project-team 4096 Feb 12 10:00 /opt/team-project

With this configuration:

  • All team members can create, edit, and read files.
  • New files automatically belong to the project-team group (setgid).
  • Users cannot delete each other’s files (sticky bit).
  • The umask of 002 ensures new files are group-writable by default.

Adding a Read-Only Auditor with ACLs

If an auditor needs to read files but not modify anything:

# Grant read-only access to the auditor user
sudo setfacl -R -m u:auditor:rX /opt/team-project

# Set default ACLs so new files also grant read access to the auditor
sudo setfacl -R -d -m u:auditor:rX /opt/team-project

# Verify
getfacl /opt/team-project

Finding Permission Issues with find

The find command is invaluable for auditing permissions across a filesystem.

Find Files by Permission

# Find all world-writable files
find / -type f -perm -o=w -not -path "/proc/*" -not -path "/sys/*" 2>/dev/null

# Find all world-writable directories (excluding standard ones)
find / -type d -perm -o=w -not -path "/tmp*" -not -path "/proc/*" -not -path "/sys/*" 2>/dev/null

# Find all setuid executables
find / -type f -perm -4000 2>/dev/null

# Find all setgid executables
find / -type f -perm -2000 2>/dev/null

# Find all setuid and setgid files combined
find / -type f \( -perm -4000 -o -perm -2000 \) -ls 2>/dev/null

Find Files with Unusual Ownership

# Find files with no valid owner (orphaned files)
find / -nouser 2>/dev/null

# Find files with no valid group
find / -nogroup 2>/dev/null

# Find files owned by a specific user
find /home -user alice -type f

# Find files not owned by a specific user in a web directory
find /var/www -not -user www-data -type f

Find Files by Permission Patterns

# Find files that are exactly 777 (a security risk)
find /var/www -type f -perm 0777

# Find files that have at least the specified permission bits set
find /opt -type f -perm -0755

# Find executable files that are not owned by root
find /usr/local/bin -type f -perm -o=x -not -user root

# Find configuration files that are world-readable
find /etc -name "*.conf" -perm -o=r -type f 2>/dev/null | head -20

Auditing Scripts

Create a simple audit script to regularly check for permission anomalies:

#!/bin/bash
# permission-audit.sh — Scan for common permission issues

echo "=== World-Writable Files ==="
find /var/www /opt /srv -type f -perm -o=w 2>/dev/null

echo ""
echo "=== Setuid Binaries (non-standard) ==="
find /usr/local -type f -perm -4000 2>/dev/null

echo ""
echo "=== Orphaned Files (no valid owner) ==="
find /home /opt /var -nouser -o -nogroup 2>/dev/null

echo ""
echo "=== Files with 777 Permissions ==="
find /var/www /opt /srv -type f -perm 0777 2>/dev/null
chmod 750 permission-audit.sh
sudo ./permission-audit.sh

Security Best Practices

Principle of Least Privilege

Always grant the minimum permissions necessary. Start restrictive and loosen only when required:

# Start with restrictive permissions
chmod 600 /etc/app/secrets.conf
chown root:root /etc/app/secrets.conf

# Only add read access for the application group if needed
chmod 640 /etc/app/secrets.conf
chown root:appgroup /etc/app/secrets.conf

Never Use 777

There is almost never a legitimate reason to set 777 on any file or directory in production. If you find yourself reaching for 777, reconsider the access model:

# WRONG — granting full access to everyone
chmod 777 /var/www/uploads   # Security vulnerability

# CORRECT — grant write only to the web server and content team
sudo chown www-data:content-team /var/www/uploads
sudo chmod 2775 /var/www/uploads

Protect Sensitive Files

# SSH keys — owner-only read/write
chmod 600 ~/.ssh/id_rsa
chmod 644 ~/.ssh/id_rsa.pub
chmod 700 ~/.ssh

# Application secrets
chmod 600 /etc/app/database.yml
chmod 600 /etc/app/.env

# SSL/TLS private keys
chmod 600 /etc/ssl/private/server.key
chown root:root /etc/ssl/private/server.key

# Sudo configuration
chmod 440 /etc/sudoers.d/custom

Audit setuid and setgid Regularly

Unauthorized setuid binaries are a common attack vector for privilege escalation:

# Record baseline setuid/setgid files
find / -type f \( -perm -4000 -o -perm -2000 \) -ls 2>/dev/null > /root/suid-baseline.txt

# Compare against baseline periodically
find / -type f \( -perm -4000 -o -perm -2000 \) -ls 2>/dev/null | diff /root/suid-baseline.txt -

Immutable Files

For files that must never be modified (even by root without explicitly unlocking them), use the chattr command:

# Make a file immutable
sudo chattr +i /etc/resolv.conf

# Verify
lsattr /etc/resolv.conf
----i--------e-- /etc/resolv.conf

# Remove the immutable flag when changes are needed
sudo chattr -i /etc/resolv.conf

Filesystem Mount Options

Enhance security by restricting entire mount points:

# In /etc/fstab — mount /tmp with noexec, nosuid, nodev
tmpfs /tmp tmpfs defaults,noexec,nosuid,nodev 0 0

# Mount a data partition read-only
/dev/sdb1 /data ext4 defaults,ro 0 2
  • noexec — Prevents execution of any binaries on this mount.
  • nosuid — Ignores the setuid and setgid bits for files on this mount.
  • nodev — Prevents device file interpretation.

Troubleshooting Common Issues

”Permission denied” When Running a Script

# Check if the file has execute permission
ls -l script.sh
-rw-r--r-- 1 user user 256 Feb 12 10:00 script.sh

# Solution 1: Add execute permission
chmod +x script.sh

# Solution 2: Run with the interpreter directly (no execute bit needed)
bash script.sh

Cannot cd Into a Directory

# A directory needs execute permission to enter
ls -ld /opt/restricted
drw-r--r-- 2 root root 4096 Feb 12 10:00 /opt/restricted

# Fix: add execute permission
sudo chmod +x /opt/restricted

Files Created with Wrong Group

If new files in a shared directory get the user’s primary group instead of the directory’s group:

# Check if setgid is set on the parent directory
ls -ld /opt/shared
drwxrwxr-x 2 root devteam 4096 Feb 12 10:00 /opt/shared

# Fix: add setgid
sudo chmod g+s /opt/shared

ACL Not Working

# Check if the filesystem supports ACLs
mount | grep $(df --output=source /opt | tail -1) | grep acl

# Check if the acl package is installed
which getfacl setfacl

# If not installed
sudo apt install acl    # Debian/Ubuntu
sudo dnf install acl    # CentOS/RHEL/Fedora

# Verify ACLs are applied
getfacl /path/to/file

Permission Lost After chmod on ACL File

Remember that chmod modifies the ACL mask, not the group permission:

# This changes the mask — may reduce effective permissions for ACL entries
chmod 644 /opt/project/file.txt

# Check the effective permissions
getfacl /opt/project/file.txt

# Restore the mask to allow full ACL permissions
setfacl -m m::rwx /opt/project/file.txt

Resolving SELinux or AppArmor Denials

If permissions look correct but access is still denied, the issue may be a mandatory access control system:

# Check if SELinux is enforcing (CentOS/RHEL/Fedora)
getenforce
# If "Enforcing", check the audit log
sudo ausearch -m AVC -ts recent

# Check AppArmor status (Debian/Ubuntu)
sudo aa-status

# Check syslog for AppArmor denials
sudo grep DENIED /var/log/syslog | tail -10

SSH Key Permissions

SSH is strict about file permissions. If SSH refuses to use your key:

# Required permissions
chmod 700 ~/.ssh
chmod 600 ~/.ssh/id_rsa
chmod 644 ~/.ssh/id_rsa.pub
chmod 600 ~/.ssh/authorized_keys
chmod 644 ~/.ssh/known_hosts
chmod 644 ~/.ssh/config

# Ownership must be the current user
chown -R $(whoami):$(whoami) ~/.ssh

Summary

Linux file permissions are a layered system that starts simple and scales to complex multi-user environments:

  1. Basic permissions (rwx) cover most single-user and small-team scenarios.
  2. Ownership (chown, chgrp) assigns files to the correct users and groups.
  3. umask controls default permissions for newly created files.
  4. Special permissions (setuid, setgid, sticky bit) solve specific security and collaboration requirements.
  5. POSIX ACLs (setfacl, getfacl) provide the fine-grained, per-user and per-group access control needed in enterprise environments.

Apply the principle of least privilege at every level. Audit permissions regularly. Use find to hunt for anomalies. Document your permission scheme so that future administrators understand the intent behind each configuration choice.