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:

bash
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:

bash
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.com

The 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:

bash
# Check the full path permissions
namei -l /var/www/html/index.html

Output 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.

bash
# Check directories
ls -la /var/www/html

If 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:

bash
# Full path check
namei -l /var/www/mysite/uploads/images/photo.jpg

If any directory in the path lacks execute permission for others, Nginx can't reach the files:

bash
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 others

Fix:

bash
# Make directories traversable
chmod 755 /var/www/mysite
chmod 755 /var/www/mysite/uploads
chmod 755 /var/www/mysite/uploads/images

Step 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:

bash
2026/04/04 11:00:23 [error] 1234#1234: *5678 directory index of "/var/www/html/" is forbidden

Check your Nginx configuration:

bash
grep -r "index" /etc/nginx/sites-available/

Ensure you have an index directive:

nginx
location / {
    root /var/www/html;
    index index.html index.htm index.php;
}

Or enable directory listing (not recommended for production):

nginx
location /files/ {
    autoindex on;
}

Step 6: Handle Unix Socket Permissions (PHP-FPM)

If you're getting permission denied on a Unix socket:

bash
connect() to unix:/run/php/php8.2-fpm.sock failed (13: Permission denied)

Check the socket permissions:

bash
ls -la /run/php/php8.2-fpm.sock

Output: `` 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:

ini
listen.owner = www-data
listen.group = www-data
listen.mode = 0660

Restart PHP-FPM:

bash
systemctl restart php8.2-fpm

Verify:

bash
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.sock

Step 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

CheckCommandFix
Nginx user`ps aux \grep nginx`Configure user in nginx.conf
File readablels -la /path/to/filechmod 644 file
Dir traversablels -lad /path/to/dirchmod 755 dir
All parent dirsnamei -l /full/pathFix each directory
Socket accessls -la /run/php/*.sockFix listen.owner in PHP-FPM
SELinux contextls -laZ /var/www/restorecon -Rv or chcon
AppArmoraa-statusAdjust 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.