Skip to content

Laravel error

Laravel: The stream or file could not be opened

The error

The stream or file "/var/www/app/storage/logs/laravel.log" could not be opened in append mode: Failed to open stream: Permission denied

Monolog could not write laravel.log. The permission is the visible problem; the reason two different users own files in that directory is the one worth fixing.

The stream or file "/var/www/app/storage/logs/laravel.log" could not be
opened in append mode: Failed to open stream: Permission denied

Monolog cannot open the log file. The application usually dies with a 500 at this point, because the failure happens while it is trying to report something else - and so the original error is never recorded.

That is the detail worth noticing. This exception is almost always standing in front of a different one.

Two users, one directory

The permission bit is a symptom. The cause is that more than one identity writes into storage/logs:

  • the web server, as www-data or nginx
  • the deploy, as whatever your CI uses
  • cron and queue workers, frequently as root because that was easiest

Whichever writes first owns the file. The rest get this.

sudo chown -R deploy:www-data storage bootstrap/cache
sudo chmod -R 775 storage bootstrap/cache
sudo find storage -type d -exec chmod g+s {} \;

The last line is the one people skip and the one that stops it coming back: the setgid bit makes new files inherit the directory's group, so a file created by cron is still writable by the web server.

chmod 777 also makes the error stop. It makes every process on the machine able to write into a directory your application reads from, which is a bad trade for five seconds of convenience.

The midnight version

With the daily driver the filename carries the date - laravel-2026-10-28.log - so a new file is created every midnight by whatever logs first.

If a nightly cron running as root gets there before the first web request, the web server cannot append to today's file, and the site starts throwing at 00:00 with nothing deployed. Teams chase this for a long time because the timing suggests a scheduled task is broken, and the scheduled task is fine - it just logged first.

Running cron and queue workers as the same user as the web server removes the whole class of problem. If they must differ, the setgid bit above is what makes it survive.

Stop the logger from hiding the real error

While you are here, make sure a logging failure cannot swallow the thing it was logging. In config/logging.php:

'stack' => [
    'driver' => 'stack',
    'channels' => ['daily', 'stderr'],
    'ignore_exceptions' => false,
],

A second channel that does not touch the filesystem means a permission problem costs you one destination rather than the whole record.

In a container, go further and drop the file entirely - stderr only, collected by the platform. There is no ownership question if nothing is written to disk, and a container filesystem is the wrong place for anything you want to keep.

What it is usually covering for

Once logging works, look at what was being logged. This error arriving in a burst almost always means the application started throwing something else at the same time, and the permissions were a pre-existing condition that only became visible when the logger was finally asked to do some work.

Infrastructure faults that present as application errors are a large share of what support work actually involves, and this is the most common one.

Related questions

Why today and not yesterday?
The daily driver names the file after the date, so at midnight it creates a new one - owned by whichever process logged first. If that was a cron running as root, the web server cannot append to it, and the application breaks at midnight with no deploy involved.
Is chmod 777 acceptable here?
No. Every process on the machine can then write to a directory your application reads back, and the usual follow-up to a foothold elsewhere is writing somewhere like this. A shared group with 775 gives the same convenience without the opening.
Why is the error a 500 rather than a missing log line?
Because the logger throws when it cannot open its handler, and that happens while the application is reporting another problem. The exception you see is the logging failure; the original error was never written down, which is what makes these incidents hard to reconstruct.
Should we log to stdout instead?
In a container, yes - the stderr driver removes the filesystem from the question entirely and lets the platform collect the output. On a traditional server it is a bigger change than it looks, because something still has to collect and rotate it.
Call us+1 848 272 7583WhatsApp+90 850 308 5436Emailinfo@codefacture.comContact page