Permission Denied: Debugging File and Directory Permissions
Diagnose and fix permission denied errors on Linux servers
Understanding Permission Errors
When you see "Permission denied," the first step is understanding what user is trying to access what file and what operation it's attempting. Read the full error message, it tells you exactly what's wrong.
Permission denied: /var/www/mysite/uploads/file.txtThis means: some process tried to access the file and was blocked by permissions.
Diagnose: Check File Permissions
View Permissions with ls
ls -la /var/www/mysite/Output example:
drwxr-xr-x 5 www-data www-data 4096 Mar 28 10:15 .
-rw-r--r-- 1 www-data www-data 2048 Mar 28 10:15 index.php
drwxr-xr-x 2 www-data www-data 4096 Mar 28 10:15 uploadsUnderstanding the Symbols
-rw-r--r-- 1 www-data www-data
^ ^ ^ ^
| | | |
| | | +-- Others (everyone else)
| | +------ Group
| +--------- Owner
+------------ File type (- = file, d = directory)Permissions breakdown: rw-r--r--
- Owner (user):
rw-(read, write, no execute) - Group:
r--(read only) - Others:
r--(read only)
Identify the Process Owner
When a web request causes a permission error, find which user is running the web server:
# For Nginx
ps aux | grep nginx
# Output:
# root 1234 0.0 0.1 12345 5678 ? Ss 10:00 nginx: master process
# www-data 1235 0.0 0.2 23456 9876 ? S 10:00 nginx: worker process
# For Apache
ps aux | grep apache
# Output:
# root 4567 0.0 0.1 45678 3456 ? Ss 10:00 /usr/sbin/apache2
# www-data 4568 0.0 0.2 56789 4567 ? S 10:00 /usr/sbin/apache2
# For PHP-FPM
ps aux | grep php
# Output:
# root 7890 0.0 0.1 67890 2345 ? Ss 10:00 php-fpm: master process
# www-data 7891 0.0 0.3 78901 3456 ? S 10:00 php-fpm: pool wwwThe worker processes run as www-data (or sometimes nobody, apache, nginx).
Standard Web Server Permissions
Recommended Permission Model
| Item | Permission | Owner | Group | Reason |
|---|---|---|---|---|
| Web root | 755 | www-data | www-data | Server needs to read/execute |
| Files | 644 | www-data | www-data | Server reads, users read |
| Directories | 755 | www-data | www-data | Server needs to list contents |
| Uploads | 755 | www-data | www-data | Server writes new files here |
| Config | 600 | www-data | www-data | Sensitive data, server-only |
Set Correct Permissions
Fix ownership:
chown -R www-data:www-data /var/www/mysiteFix permissions (directories):
find /var/www/mysite -type d -exec chmod 755 {} \;Fix permissions (files):
find /var/www/mysite -type f -exec chmod 644 {} \;Or as a one-liner:
chmod -R 755 /var/www/mysite && find /var/www/mysite -type f -exec chmod 644 {} \;Test Permissions As the Actual User
Don't just test as root. Test as the actual process user to catch permission issues:
# Test if www-data can read the file
sudo -u www-data cat /var/www/mysite/config.php
# Test if www-data can write to uploads
sudo -u www-data touch /var/www/mysite/uploads/test.txt
sudo -u www-data rm /var/www/mysite/uploads/test.txt
# Test directory listing
sudo -u www-data ls -la /var/www/mysite/If any of these fail, you've found the permission issue.
Common Problem Areas
1. /var/lib/php/sessions
PHP-FPM needs write access:
ls -la /var/lib/php/
# Should show: drwx------ 2 www-data www-data
chmod 700 /var/lib/php/sessions
chown -R www-data:www-data /var/lib/php/sessions2. /var/log
Application and web server logs:
# Nginx
chmod 755 /var/log/nginx
chown -R root:adm /var/log/nginx
# Apache
chmod 755 /var/log/apache2
chown -R root:adm /var/log/apache23. /tmp and /var/run
Temporary files and sockets:
ls -la /tmp | head
ls -la /var/run | head
# Should be world-writable or owned by the process user
chmod 1777 /tmp4. Socket Files
For PHP-FPM or other services using sockets:
ls -la /var/run/php-fpm.sock
# Should show: srw-rw---- 1 www-data www-data
# If wrong permissions:
chmod 660 /var/run/php-fpm.sock
chown www-data:www-data /var/run/php-fpm.sockAdvanced: SELinux and AppArmor
If permissions look correct but errors persist, check mandatory access control systems:
SELinux (RHEL, CentOS, Fedora)
Check if enabled:
getenforce
# Output: Enforcing, Permissive, or DisabledView SELinux context:
ls -Z /var/www/mysite
# Output: -rw-r--r-- www-data www-data unconfined_u:object_r:httpd_sys_content_t:s0 file.txtSet correct context:
chcon -R -t httpd_sys_rw_content_t /var/www/mysite/uploadsRestore default contexts:
restorecon -R -v /var/www/mysiteAppArmor (Ubuntu, Debian)
Check if enabled:
aa-statusView loaded profiles:
aa-status | grep nginx
aa-status | grep apache2Check if AppArmor is blocking:
tail -f /var/log/syslog | grep apparmorACLs (Access Control Lists)
For complex permission requirements:
View ACLs
getfacl /var/www/mysite/Set ACLs
# Give www-data group write access
setfacl -m g:www-data:rwx /var/www/mysite/uploadsSpecial Permission Bits
Sticky Bit (for shared directories)
Prevents users from deleting files they don't own:
# Add sticky bit
chmod +t /var/www/mysite/uploads
# Same as
chmod 1755 /var/www/mysite/uploads
# View
ls -la /var/www/mysite/
# Shows: drwxr-xr-t (note the 't')SetUID / SetGID
Run a file as its owner (not recommended for web):
# SetUID - rarely needed for web
chmod u+s /path/to/file
# SetGID - sometimes used for uploads
chmod g+s /var/www/mysite/uploadsNever use chmod 777 in production. It makes files world-writable, creating massive security risks. Always use the minimal required permissions: 755 for directories, 644 for files.
Real-World Troubleshooting Example
Scenario: WordPress upload fails with "Permission denied" error.
Step 1: Check who runs Nginx
ps aux | grep nginx
# www-data is running NginxStep 2: Check uploads directory permissions
ls -la /var/www/wordpress/
# drwxr-xr-x 2 root root uploads ← WRONG: owned by root, not www-dataStep 3: Test as www-data
sudo -u www-data touch /var/www/wordpress/uploads/test.txt
# touch: cannot touch '/var/www/wordpress/uploads/test.txt': Permission deniedStep 4: Fix ownership
chown -R www-data:www-data /var/www/wordpress/uploads
chmod 755 /var/www/wordpress/uploadsStep 5: Test again
sudo -u www-data touch /var/www/wordpress/uploads/test.txt
# Success: file createdStep 6: Upload now works
Quick Diagnostic Checklist
- Read the full error message, what file, what user?
- Check file/directory existence:
ls -la /path/to/file - Check owner:
ls -lshows owner and group - Check process user:
ps aux | grep nginx - Test as process user:
sudo -u www-data cat /path/to/file - Verify Nginx/Apache is running as correct user
- Fix ownership:
chown -R www-data:www-data /var/www/ - Fix permissions:
chmod 755 /var/www && find /var/www -type f -exec chmod 644 {} \; - Check SELinux/AppArmor if still failing:
getenforce,aa-status - Review logs:
/var/log/nginx/error.log,/var/log/apache2/error.log