Ffile2fix
Sign in Get started

Fix PHP "Failed to open stream: Permission denied"

Warning: fopen(/var/www/html/storage/cache/data.json): Failed to open stream: Permission denied in /var/www/html/app/Cache.php on line 27

PHP runs as a specific system user (often www-data, nginx, apache or your account under PHP-FPM) and that user lacks permission to read, write or create the file in the message. Give the PHP user ownership or group write access to the directories it needs, rather than opening them to everyone.

Also appears as: file_put_contents(/var/www/html/storage/logs/laravel.log): Failed to open stream: Permission denied · PHP Warning: include(/var/www/html/config.php): Failed to open stream: Permission denied · move_uploaded_file(): Unable to move '/tmp/phpA1b2C3' to '/var/www/html/uploads/photo.jpg' · open() "/var/www/html/index.php" failed (13: Permission denied)

Common causes

  • The file or its directory is owned by root or a deploy user and PHP's user cannot write to it
  • A parent directory lacks the execute (x) bit, so PHP cannot traverse the path
  • Files were created by a CLI command or cron job running as a different user
  • SELinux on RHEL-based systems blocks web writes unless the path has httpd_sys_rw_content_t
  • The target directory does not exist, so PHP tries to create a file in a non-writable parent
  • A read-only filesystem or container volume mounted without write access

How to fix it

  1. Find out which user PHP runs as. Run ps -o user= -C php-fpm8.3 (or check user = in the FPM pool file). In a script, echo posix_getpwuid(posix_geteuid())['name'];.
  2. Inspect the full path. Run namei -l /var/www/html/storage/cache/data.json to see owner and mode of each directory. Every directory needs x for the PHP user, and the final directory needs w to create files.
  3. Fix ownership of writable directories. Give the PHP user ownership of only the writable folders, for example sudo chown -R www-data:www-data storage bootstrap/cache or wp-content/uploads. Keep code files owned by the deploy user.
  4. Set sane modes. Use 775 for shared writable directories and 664 for files when deploy user and PHP share a group, or 755/644 when PHP owns them. Avoid 777.
  5. Fix SELinux labels. On RHEL, Alma or Rocky run sudo semanage fcontext -a -t httpd_sys_rw_content_t "/var/www/html/storage(/.*)?" and sudo restorecon -Rv /var/www/html/storage. Check denials with sudo ausearch -m avc -ts recent.
  6. Run CLI tasks as the web user. Run artisan, WP-CLI or cron scripts with sudo -u www-data so new files get the same owner as files created by web requests.

Shell (Laravel-style writable dirs)

sudo chown -R deploy:www-data /var/www/html
sudo find /var/www/html -type d -exec chmod 755 {} +
sudo find /var/www/html -type f -exec chmod 644 {} +
sudo chmod -R ug+rwX /var/www/html/storage /var/www/html/bootstrap/cache
namei -l /var/www/html/storage/logs

How to stop it happening again

  • Set ownership and permissions in your deploy script, not by hand
  • Run cron jobs and CLI commands as the same user as PHP-FPM
  • Keep writable data in a few dedicated directories
  • Use setgid on shared directories (chmod g+s) so new files inherit the group

Frequently asked questions

Why not just chmod 777?

777 lets any process or user on the server modify the file, which attackers abuse to plant code. It also hides the real ownership problem, which comes back on the next deploy.

It works from the command line but not in the browser. Why?

On the command line PHP runs as your login user, while web requests run as the PHP-FPM or Apache user. Check permissions for that user, not yours.

Is open_basedir the same problem?

No. open_basedir gives a different message ("open_basedir restriction in effect") and means PHP is configured to block paths outside allowed directories, regardless of file permissions.