failed to open stream high
PHP Warning: failed to open stream: Permission denied
PHP can't read or include a file because the web-server user lacks filesystem access, often cascading into a fatal include error.
What you see
PHP Warning: include(/var/www/html/config.php): Failed to open stream: Permission denied in /var/www/html/index.php on line 12 PHP Fatal error: Failed opening required '/var/www/html/config.php'
What’s actually happening
A page renders blank or half-built, and the PHP error log shows the warning with an absolute path. With `include` or `fopen` you get a warning and execution limps on; with `require` it's fatal and the request dies right there. The file almost always exists — `ls` finds it fine — which is what throws people off. The blocker is the web-server user, not a missing file.
Common causes
- The file or a parent directory is owned by root or a deploy user, while PHP runs as www-data/apache/nginx and can't read it.
- A parent directory is missing its execute bit (750 instead of 755), so the user can't traverse into it even when the file itself is readable.
- SELinux on RHEL/Rocky/Alma is blocking httpd because of a wrong file context, e.g. files labeled user_home_t instead of httpd_sys_content_t.
- open_basedir in php.ini fences PHP into specific directories and the target path sits outside them.
- The file lives under systemd PrivateTmp or on an NFS/CIFS mount, so the PHP-FPM process sees a different filesystem view than your shell does.
How to fix it
- Find which user PHP runs as, then test access as that userCheck the FPM pool: `grep -E '^user|^group' /etc/php/*/fpm/pool.d/www.conf`. Then `sudo -u www-data cat /var/www/html/config.php`. If that command fails, you've reproduced the bug at the shell and it's purely permissions.
- Fix ownership and permissions on the file and every parent`chown -R www-data:www-data /var/www/html`, then `find /var/www/html -type d -exec chmod 755 {} \;` and `find /var/www/html -type f -exec chmod 644 {} \;`. The directory 755 is the part people miss — without the dir execute bit, a readable file is still unreachable.
- Check SELinux if you're on the RHEL family`getenforce` shows whether it's enforcing. `ls -Z` shows the file's context. Relabel with `chcon -R -t httpd_sys_content_t /var/www/html`; make it survive a relabel via `semanage fcontext -a -t httpd_sys_content_t '/var/www/html(/.*)?'` then `restorecon -Rv`. `ausearch -m avc -ts recent` prints the exact denial.
- Verify open_basedir and the resolved path`php -i | grep open_basedir`. If it's set, the included path has to live inside it — a symlink pointing outside counts as outside. Also sanity-check the path itself: a wrong `__DIR__` base or a typo can resolve to a real file the user genuinely can't reach.
- Rule out PrivateTmp for /tmp pathsIf the file is under /tmp, check `systemctl show php8.2-fpm -p PrivateTmp`. When it's true, PHP gets a private /tmp that isn't the system one, so your file isn't there from its view. Write to an app-owned directory instead of /tmp.
Stop it recurring
Deploy under one owner and let the web server read through group membership, so a stray root-owned file never lands in the docroot.
Related errors