Permission Denied: Debugging File and Directory Permissions
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.txt
This 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 uploads
Understanding 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 www
The 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/mysite
Fix 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/sessions
2. /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/apache2
3. /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 /tmp
4. 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.sock
Advanced: 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 Disabled
View 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.txt
Set correct context:
chcon -R -t httpd_sys_rw_content_t /var/www/mysite/uploads
Restore default contexts:
restorecon -R -v /var/www/mysite
AppArmor (Ubuntu, Debian)
Check if enabled:
aa-status
View loaded profiles:
aa-status | grep nginx
aa-status | grep apache2
Check if AppArmor is blocking:
tail -f /var/log/syslog | grep apparmor
ACLs (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/uploads
Special 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/uploads
Never 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 Nginx
Step 2: Check uploads directory permissions
ls -la /var/www/wordpress/
# drwxr-xr-x 2 root root uploads ← WRONG: owned by root, not www-data
Step 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 denied
Step 4: Fix ownership
chown -R www-data:www-data /var/www/wordpress/uploads
chmod 755 /var/www/wordpress/uploads
Step 5: Test again
sudo -u www-data touch /var/www/wordpress/uploads/test.txt
# Success — file created
Step 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 -l shows 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
Related articles
Locked Out of VPS
Complete guide to recover server access when locked out, with step-by-step instructions from VNC Console
Server Unreachable
What to do when server is not responding or you can't connect via SSH
Website Not Reachable
What to do when website is not responding, shows errors, or is unreachable
