Setting Recursive Chmod Permissions
The Recursive Permission Problem
Running chmod -R 755 /var/www seems straightforward — apply 755 to everything under /var/www. But this creates a common problem: 755 makes files executable, which is unnecessary and potentially dangerous for regular files. You usually want directories at 755 (rwxr-xr-x) and files at 644 (rw-r--r--).
A single chmod -R can't set different permissions for files versus directories. You need more targeted approaches.
The Standard Solution: Find + Chmod
The most widely used pattern separates files and directories:
# Directories: 755 (rwxr-xr-x)
find /var/www -type d -exec chmod 755 {} +
# Files: 644 (rw-r--r--)
find /var/www -type f -exec chmod 644 {} +
The {} + syntax batches matched paths into fewer chmod calls, making it significantly faster than {} \; for large directory trees.
Performance Comparison
| Command Calls | 10,000 Files | 100,000 Files |
|---|---|---|
{} \; (one per file) | ~10,000 chmod calls | ~100,000 chmod calls |
{} + (batched) | ~100 chmod calls | ~1,000 chmod calls |
Use {} + unless you have a specific reason for per-file execution.
Using Xargs for Large Trees
For very large directory trees, xargs can be faster than -exec:
find /var/www -type d -print0 | xargs -0 chmod 755
find /var/www -type f -print0 | xargs -0 chmod 644
The -print0 and -0 flags handle filenames with spaces or special characters safely. Never omit these flags when dealing with user-uploaded content or uncontrolled filenames.
The Chmod X Trick
The X (capital X) permission flag sets execute only on directories and files that already have execute permission:
chmod -R a+X /var/www
This is useful when you want to add execute to directories without making all files executable. However, it has a catch: files that already have any execute bit set will keep it. If your files are already 755 by mistake, a+X won't fix them — it preserves the existing execute bits.
Safe Pattern: Reset Then Apply
# First, strip all execute bits
chmod -R a-x /var/www
# Then add execute back for directories only
find /var/www -type d -exec chmod a+x {} +
Handling Special File Types
Executable Scripts
Scripts (.sh, .py, .js with shebangs) need execute permission:
# After setting 644 on all files, add execute back for scripts
find /var/www -type f -name "*.sh" -exec chmod 755 {} +
Web Server Document Roots
A secure web root typically follows this pattern:
| Path | Permissions | Owner | Group |
|---|---|---|---|
/var/www/ | 755 | root | www-data |
/var/www/html/ | 755 | www-data | www-data |
| Directories | 755 | www-data | www-data |
| Static files | 644 | www-data | www-data |
| Upload directories | 755 | www-data | www-data |
| Config files | 640 | root | www-data |
Shared Group Directories
For directories where a group needs write access:
# Set SGID bit so new files inherit the group
find /var/www/shared -type d -exec chmod 2775 {} +
find /var/www/shared -type f -exec chmod 664 {} +
The 2 (SGID) ensures new files are created with the directory's group rather than the creator's primary group.
Common Recursive Mistakes
1. Chmodding from Root
# DANGEROUS — breaks system binaries and configs
chmod -R 755 /
Always specify your target directory explicitly. Never run recursive chmod on / or /etc.
2. Forgetting Hidden Files
chmod -R processes hidden files, but find patterns like *.txt won't match .env. If you need to exclude hidden files:
find /var/www -type f -not -path '*/\.*' -exec chmod 644 {} +
3. Modifying Symlink Targets
chmod follows symlinks and modifies the target file. This can accidentally change permissions outside your intended directory tree. Use -P with find to avoid following symlinks:
find -P /var/www -type f -exec chmod 644 {} +
Dry Run First
Before running recursive chmod, preview what would change:
# List all files that would be modified
find /var/www -type f ! -perm 644 -print
find /var/www -type d ! -perm 755 -print
Only apply changes if the list looks correct.
Related Guides
Try It Yourself
Calculate chmod values visually and see exactly what each permission set allows. Our free tool converts between numeric, symbolic, and binary representations.