You just deployed your static website or configured a new location block, but when you visit the page, you get a 403 Forbidden error. The Nginx error log shows "permission denied" even though the files are definitely there. This is one of the most common Nginx issues, and it's almost always a permission problem at some layer.
Let's trace through the permission chain and fix it.
Understanding the Permission Error
The error typically shows up in /var/log/nginx/error.log like this:
2026/04/04 11:00:23 [error] 1234#1234: *5678 open() "/var/www/html/index.html" failed (13: Permission denied), client: 192.168.1.100, server: example.com, request: "GET / HTTP/1.1"Or for directories:
2026/04/04 11:00:23 [error] 1234#1234: *5678 directory index of "/var/www/html/" is forbidden, client: 192.168.1.100, server: example.comThe error code 13 is Linux's EACCES—permission denied at the system level.
Step 1: Identify the Nginx User
First, confirm which user Nginx runs as:
```bash # Check the main configuration grep -E "^user" /etc/nginx/nginx.conf
# Or check the running process ps aux | grep nginx | grep -v grep ```
Output:
``
nginx 1234 0.0 0.1 12345 6789 ? Ss 10:00 0:00 nginx: master process
nginx 1235 0.0 0.2 12345 7890 ? S 10:00 0:00 nginx: worker process
The default user is typically nginx on RHEL/CentOS and www-data on Debian/Ubuntu.
Step 2: Check File and Directory Permissions
Nginx needs read permission on files and execute permission on ALL parent directories:
# Check the full path permissions
namei -l /var/www/html/index.htmlOutput showing the problem:
``
drwxr-xr-x root root /
drwxr-xr-x root root var
drwxr-xr-x root root www
drwxr-xr-x root root html
-rw------- bob bob index.html
The file is readable only by owner bob. Nginx (running as nginx user) can't read it.
Fix file permissions:
```bash # Make files readable by everyone chmod 644 /var/www/html/index.html
# Or better, change ownership to nginx user chown nginx:nginx /var/www/html/index.html
# Fix all files in directory find /var/www/html -type f -exec chmod 644 {} \; find /var/www/html -type f -exec chown nginx:nginx {} \; ```
Step 3: Check Directory Execute Permission
This is the most commonly overlooked issue. Nginx needs execute permission on directories to traverse them—not just read permission.
# Check directories
ls -la /var/www/htmlIf you see:
``
drw-r--r-- 2 nginx nginx 4096 Apr 4 11:00 html
The directory lacks execute permission (x). Fix it:
```bash # Add execute permission to directories chmod 755 /var/www/html
# Fix all directories find /var/www -type d -exec chmod 755 {} \; ```
Step 4: Check Parent Directory Permissions
Nginx needs execute permission on every directory from root to your files:
# Full path check
namei -l /var/www/mysite/uploads/images/photo.jpgIf any directory in the path lacks execute permission for others, Nginx can't reach the files:
drwxr-xr-x root root /
drwxr-xr-x root root var
drwxr-xr-x root root www
drwx------ myuser myuser mysite <-- Problem: no execute for othersFix:
# Make directories traversable
chmod 755 /var/www/mysite
chmod 755 /var/www/mysite/uploads
chmod 755 /var/www/mysite/uploads/imagesStep 5: Check for Index File Issues
If you're getting "directory index is forbidden", Nginx can't find an index file and directory listing is disabled:
2026/04/04 11:00:23 [error] 1234#1234: *5678 directory index of "/var/www/html/" is forbiddenCheck your Nginx configuration:
grep -r "index" /etc/nginx/sites-available/Ensure you have an index directive:
location / {
root /var/www/html;
index index.html index.htm index.php;
}Or enable directory listing (not recommended for production):
location /files/ {
autoindex on;
}Step 6: Handle Unix Socket Permissions (PHP-FPM)
If you're getting permission denied on a Unix socket:
connect() to unix:/run/php/php8.2-fpm.sock failed (13: Permission denied)Check the socket permissions:
ls -la /run/php/php8.2-fpm.sockOutput:
``
srw-rw---- 1 root root 0 Apr 4 11:00 /run/php/php8.2-fpm.sock
The socket is owned by root with no access for nginx user. Fix in /etc/php/8.2/fpm/pool.d/www.conf:
listen.owner = www-data
listen.group = www-data
listen.mode = 0660Restart PHP-FPM:
systemctl restart php8.2-fpmVerify:
ls -la /run/php/php8.2-fpm.sock
# Should show:
# srw-rw---- 1 www-data www-data 0 Apr 4 11:00 /run/php/php8.2-fpm.sockStep 7: Check SELinux Contexts (RHEL/CentOS/Fedora)
If permissions look correct but you still get denied, SELinux is likely blocking access:
```bash # Check if SELinux is enforcing getenforce
# Check file contexts ls -laZ /var/www/html/ ```
Output:
``
-rw-r--r--. nginx nginx unconfined_u:object_r:user_home_t:s0 index.html
The user_home_t context is wrong for web files. Fix:
```bash # Reset to correct web context restorecon -Rv /var/www/html
# Or manually set context chcon -R -t httpd_sys_content_t /var/www/html
# For writable directories (uploads, etc.) chcon -R -t httpd_sys_rw_content_t /var/www/html/uploads ```
Check if SELinux allows Nginx to make network connections:
```bash # Check for denials ausearch -m avc -ts recent | grep nginx
# Allow Nginx network connections setsebool -P httpd_can_network_connect 1
# Allow Nginx to serve files from home directories setsebool -P httpd_read_user_content 1 ```
Step 8: Check AppArmor (Ubuntu/Debian)
AppArmor can also restrict Nginx:
```bash # Check AppArmor status aa-status
# Check if nginx profile is enforcing aa-status | grep nginx ```
If there's a restrictive profile, check /etc/apparmor.d/usr.sbin.nginx and adjust or disable:
```bash # Temporarily disable (for testing) ln -s /etc/apparmor.d/usr.sbin.nginx /etc/apparmor.d/disable/ apparmor_parser -R /etc/apparmor.d/usr.sbin.nginx
# Reload after changes systemctl reload apparmor ```
Step 9: Verify and Test
After making changes:
```bash # Test as the nginx user sudo -u nginx cat /var/www/html/index.html
# Check Nginx can access the path sudo -u nginx namei -l /var/www/html/index.html
# Reload Nginx nginx -t && systemctl reload nginx
# Test the site curl -I http://localhost/ ```
Quick Permission Setup Script
For a standard static site:
```bash # Set ownership chown -R nginx:nginx /var/www/html
# Set permissions (files readable, dirs traversable) find /var/www/html -type d -exec chmod 755 {} \; find /var/www/html -type f -exec chmod 644 {} \;
# For writable directories (uploads, cache) find /var/www/html/uploads -type d -exec chmod 775 {} \; find /var/www/html/uploads -type f -exec chmod 664 {} \;
# For SELinux systems restorecon -Rv /var/www/html ```
Permission Checklist
| Check | Command | Fix | |
|---|---|---|---|
| Nginx user | `ps aux \ | grep nginx` | Configure user in nginx.conf |
| File readable | ls -la /path/to/file | chmod 644 file | |
| Dir traversable | ls -lad /path/to/dir | chmod 755 dir | |
| All parent dirs | namei -l /full/path | Fix each directory | |
| Socket access | ls -la /run/php/*.sock | Fix listen.owner in PHP-FPM | |
| SELinux context | ls -laZ /var/www/ | restorecon -Rv or chcon | |
| AppArmor | aa-status | Adjust profile or disable |
Permission issues are methodical—start from the file and work your way up the directory tree, ensuring Nginx has access at every level.