How to Recursively Change Permissions for Directories (755) and Files (644) in Linux
When managing Linux servers or deploying web applications (such as Nginx, Apache, WordPress, or Laravel), adjusting filesystem permissions is one of the most common operational tasks.
Yet when encountering permission bottlenecks, many developers make the dangerous mistake of executing blanket recursive commands:
chmod -R 777 /var/www/html # Extremely dangerous! Grants write and execute to everyone
Or equally problematic:
chmod -R 755 /var/www/html # Makes regular files executable (+x)
chmod -R 644 /var/www/html # Complete disaster! Strips execute bits from directories, breaking navigation
Why does a blunt chmod -R fail? How do you elegantly and securely set directories to 755 while simultaneously keeping regular files at 644? This guide breaks down the underlying Linux principles and production-tested patterns.
1. Core Principle: Why Directories and Files Demand Different Permissions
In the Linux filesystem, the execute bit (x, octal value 1) has completely different meanings when applied to files versus directories:
- For Regular Files:
r(read, 4): Read file contents;w(write, 2): Modify or overwrite file contents;x(execute, 1): Run the file as an executable program or script.- The vast majority of web assets (
.html,.css,.jpg,.php,.js) should never have execute permissions. Granting execute privileges to non-executables is unnecessary and introduces security vulnerabilities.
- For Directories:
r(read, 4): List the names of files inside the directory (e.g., runningls);w(write, 2): Create, delete, or rename entries inside the directory;x(execute, 1, Crucial!): The passport to enter (cd) and traverse the directory to read file metadata.- If a directory lacks the execute bit, even if you have read permissions, you cannot change into that directory or open any files inside it (resulting in
Permission denied).
Therefore, the universal golden standard for web deployments is:
- All Directories:
755(drwxr-xr-x— owner can read/write/enter; group and others can read/enter) - All Files:
644(-rw-r--r--— owner can read/write; group and others can only read)
2. Three Classic Implementation Methods
Method 1: find with -exec ... {} + (Modern Recommended Standard ⭐)
This is the cleanest, most robust, and highest-performing syntax across modern Linux and Unix distributions:
# 1. Recursively set all directories to 755
find /path/to/base/dir -type d -exec chmod 755 {} +
# 2. Recursively set all regular files to 644
find /path/to/base/dir -type f -exec chmod 644 {} +
Why terminate with + instead of \;?
- If you use
-exec chmod 755 {} \;:findspawns a brand newchmodprocess for every single item it discovers. If your tree contains 10,000 files, it callschmod10,000 times, creating massive CPU overhead. - If you use
-exec chmod 755 {} +:findbundles thousands of matched paths into batched arguments (similar toxargs), invokingchmodonly a few times. It is blazing fast and handles paths with spaces or special characters safely.
Method 2: find -print0 piped to xargs -0 (Classic Pipeline Approach)
For massive directory trees or legacy shell scripts, this classic pipeline approach guarantees absolute delimiter safety:
# For directories
find /path/to/base/dir -type d -print0 | xargs -0 chmod 755
# For files
find /path/to/base/dir -type f -print0 | xargs -0 chmod 644
-print0: Delimits each found path using the null character (\0) rather than standard newlines or spaces.xargs -0: Instructsxargsto unpack arguments using the null byte.- Advantage: Filenames containing spaces, quotes, or strange characters are parsed with 100% fidelity without escaping bugs.
Method 3: Command Substitution chmod $(find ...) (⚠️ Avoid in Production)
Some quick guides suggest using shell command substitution:
chmod 755 $(find /path/to/base/dir -type d) # Risky!
chmod 644 $(find /path/to/base/dir -type f) # Risky!
Why is this problematic?
- Word Splitting: If a folder is named
My Documents, the shell splits it into two distinct tokens (MyandDocuments), causingchmodto fail withNo such file or directory. - Buffer Limits (
Argument list too long): The Linux kernel imposes a limit (ARG_MAX) on command-line argument lengths. If your directory contains tens of thousands of items, the command crashes completely.
3. The Hacker’s One-Liner: Leveraging Capital X (Symbolic Mode)
In addition to find, chmod provides a brilliant, built-in symbolic modifier: capital X (Special Execute):
chmod -R u=rwX,go=rX /path/to/base/dir
What is the magic of capital X?
Under POSIX chmod specifications:
- Lowercase
x: Unconditionally grants the execute bit. - Capital
X: Grants execute permissions ONLY if the target is a directory OR already has execute permissions for some user class.
Therefore, u=rwX,go=rX means:
- Owner (
u): Read/write (rw), plus execute (X) only if it’s a directory; - Group and Others (
go): Read (r), plus execute (X) only if it’s a directory.
This single command sets directories to 755 while leaving unexecutable files safely at 644 without invoking subshells or pipelines!
4. Production Recipe: Web Application Permissions
In real-world web deployment pipelines (such as Laravel or WordPress), a clean permissions initialization script looks like this:
PROJECT_DIR="/var/www/my-project"
# 1. Assign ownership to the web server user (www-data on Debian/Ubuntu; nginx or apache on RHEL)
sudo chown -R www-data:www-data "$PROJECT_DIR"
# 2. Standardize base permissions: 755 for directories, 644 for files
sudo find "$PROJECT_DIR" -type d -exec chmod 755 {} +
sudo find "$PROJECT_DIR" -type f -exec chmod 644 {} +
# 3. Grant write permissions exclusively to dedicated upload/cache directories
# Example for Laravel:
sudo chmod -R 775 "$PROJECT_DIR/storage"
sudo chmod -R 775 "$PROJECT_DIR/bootstrap/cache"
Conclusion
Permissions are the cornerstone of Linux system security. Keep this rule of thumb in mind: “Never execute regular files; always keep directories traversable.” By mastering find ... -exec chmod {} + or the symbolic capital X, you can enforce robust permissions across any Linux environment quickly and safely.