How to fix 'opendir() failed (13: permission denied)' in Nginx proxy cache
Back to blog

How to fix 'opendir() failed (13: permission denied)' in Nginx proxy cache

9/5/2026 · 2 min · Servers

When managing caching layers and reverse proxy configurations in Nginx, you may encounter recurrent critical error entries in error.log:

2026/04/02 15:56:14 [crit] 34814#0: opendir() "/www/server/nginx/proxy_cache_dir/f" failed (13: Permission denied)
2026/04/02 15:56:14 [crit] 34814#0: opendir() "/www/server/nginx/proxy_cache_dir/c" failed (13: Permission denied)
2026/04/02 15:56:14 [crit] 34814#0: opendir() "/www/server/nginx/proxy_cache_dir/d" failed (13: Permission denied)

Here is an analysis of what happens under the hood between Nginx worker processes and the Linux kernel, how to trace the root cause, and the step-by-step fix.

1. What happens under the hood?#

When configuring proxy_cache_path, Nginx partitions cache entries into a hierarchy of hash-based subdirectories (such as /f, /c, /d).

The lifecycle of cached items is handled by background workers: the Cache Manager (which purges stale items when storage thresholds are reached) and active Workers. To traverse this directory tree, Nginx invokes the opendir() library function.

At the kernel level, this triggers the openat() syscall with O_DIRECTORY. For Linux Discretionary Access Control (DAC) validation:

  1. Execute Permission (+x) on parent paths: Without +x, processes cannot traverse directory trees into cache destinations.
  2. Read Permission (+r) on subdirectories: Without +r, the getdents64 syscall is rejected, returning kernel error EACCES (code 13: Permission denied).

The Nginx process model#

Nginx boots a Master process (running as root), while request Workers and Cache Managers drop privileges to an unprivileged user (such as www, nginx, or nobody, defined by the user directive in nginx.conf).

If cache directories were created manually under root or if the user directive was modified without recursive permission updates, workers lose access.

2. Terminal diagnostic workflow#

To inspect what the operating system is blocking:

1. Check worker uid/gid#

Using the worker PID from the error log (34814):

grep -E '^Uid|^Gid' /proc/34814/status

Or checking open file descriptors:

lsof -p 34814 | grep proxy_cache_dir

2. Tracing syscalls with strace#

To catch the exact point where openat fails:

strace -p 34814 -e trace=openat,getdents64 2>&1 | grep EACCES

3. Verify extended filesystem attributes#

Ensure no immutable attributes are set:

lsattr -d /www/server/nginx/proxy_cache_dir/

3. Step-by-step remediation#

Step 1: Fix recursive ownership#

Extract the configured Nginx user and assign ownership across the cache tree:

# Extract user from nginx.conf
NGINX_USER=$(grep -P '^user\s+\K[^;]+' /www/server/nginx/conf/nginx.conf | awk '{print $1}')

# Apply correct ownership recursively
sudo chown -R $NGINX_USER:$NGINX_USER /www/server/nginx/proxy_cache_dir

Step 2: Ensure directory traversal permissions (+x)#

Ensure all directory nodes have traversal permissions:

sudo find /www/server/nginx/proxy_cache_dir -type d -exec chmod 700 {} +

Step 3: SELinux context adjustment (if applicable)#

On RHEL, CentOS, or Rocky Linux systems with active SELinux, set the writeable web context (httpd_sys_rw_content_t):

sudo semanage fcontext -a -t httpd_sys_rw_content_t "/www/server/nginx/proxy_cache_dir(/.*)?"
sudo restorecon -Rv /www/server/nginx/proxy_cache_dir

Step 4: Verify filesystem mount flags#

If caching to a dedicated NVMe/SSD mount point, verify the filesystem did not mount in read-only (ro) mode:

mount | grep proxy_cache_dir

Best practices for Nginx cache permissions#

  1. Avoid manual folder creation as root: Allow Nginx to generate cache structures automatically or apply chown immediately.
  2. Audit permissions after modifying nginx.conf: If the user directive changes, update ownership across all temporary directories (client_body_temp, proxy_temp_dir, and proxy_cache_dir).
  3. Monitor logs after reload: After running nginx -s reload, monitor error.log to confirm Cache Manager initialization completes cleanly.

Was this article helpful?

Leave a quick reaction to help prioritize future technical guides:

CC BY-NC

This post is licensed under CC BY-NC.

Comments

Join the discussion below.

0 comments