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:
- Execute Permission (
+x) on parent paths: Without+x, processes cannot traverse directory trees into cache destinations. - Read Permission (
+r) on subdirectories: Without+r, thegetdents64syscall is rejected, returning kernel errorEACCES(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#
- Avoid manual folder creation as root: Allow Nginx to generate cache structures automatically or apply
chownimmediately. - Audit permissions after modifying nginx.conf: If the
userdirective changes, update ownership across all temporary directories (client_body_temp,proxy_temp_dir, andproxy_cache_dir). - Monitor logs after reload: After running
nginx -s reload, monitorerror.logto confirm Cache Manager initialization completes cleanly.
Was this article helpful?
Leave a quick reaction to help prioritize future technical guides:
This post is licensed under CC BY-NC.



Comments
Join the discussion below.
0 comments