0% прочитано

Laravel Log Permissions: Why a Daily Log Created by deploy Became Unwritable for www-data

In ICanUp production, deployment commands and PHP-FPM run as different system users. The Laravel logging problem appeared when a new daily log was created by deploy with mode 0644 and www-data could no longer append to it. This case explains ownership, group permissions, setgid, 0664, umask, and why chmod 777 is not the right fix.

17 вересня 2026 р. 10 хв читанняDevOps

A Laravel application can work normally all day and suddenly start failing to write into storage/logs after the date changes.

In ICanUp production, the cause was not Laravel logging itself. The cause was at the Linux level: the same writable directory was being used by processes running as different system users.

Deployment and CLI commands could run as deploy, while HTTP requests through PHP-FPM ran as www-data. If deploy created the new daily log first with mode 0644, www-data could no longer append to it later.

This is not a problem that should be solved with chmod 777. The correct fix is to make every process that legitimately writes Laravel logs share a compatible ownership, group, and permission contract.
Laravel production log permission flow with deploy creating a daily log and www-data receiving Permission denied because the file is mode 0644
One directory, two system users. The failure occurs when a new file has the correct group but does not grant that group write permission.

The Symptom Looked Like a Laravel Problem

At the application level, the symptom is simple: Laravel tries to append to the daily log file and cannot open it for writing.

Typical symptom
storage/logs/laravel-YYYY-MM-DD.log Permission deniedCould not be opened in append mode

If you look only at the Laravel exception, it is easy to start investigating Monolog, channel configuration, or application code.

The first useful check is much simpler: who owns the file, and what are its permissions?

First Check: Owner, Group, and Mode

BASH - Inspect Laravel log permissions
cd /var/www/icanup stat -c '%A %a %U:%G %n' \  storage/logs \  storage/logs/laravel-*.log

The problematic file conceptually looked like this:

Problematic file
-rw-r--r-- 644 deploy:www-data storage/logs/laravel-YYYY-MM-DD.log

At first glance, the group looks correct: www-data.

But 0644 means only the file owner can write. The group has read permission only.

Why deploy Can Write but www-data Cannot

Process

System user

Responsibility

Deployment and Artisan CLI

deploy

Updates the application, runs Artisan commands, and may also create log files

PHP-FPM

www-data

Handles HTTP requests and writes application logs

Queue worker

Depends on production configuration

May also write into the same logs

Neither of these users is inherently wrong. Both have a legitimate role.

The problem appears when a shared resource does not have an explicit permission contract.

0644 Explains Most of the Failure

Class

Permissions

Owner

rw- - read and write

Group

r-- - read only

Others

r-- - read only

For a deploy:www-data file with mode 0644, deploy can write.

PHP-FPM running as www-data receives the group permissions, and the group has no write bit.

A correct group without group write permission is therefore not enough.

Why the Problem Could Return Only When a New Daily Log Was Created

Laravel's daily channel creates a new file for a new day.

The previous file may have correct permissions after a manual fix. The next day, however, a deployment or Artisan process running as deploy may be the first process to trigger logging, and the new file can be created with a different mode again.

That is why fixing only the existing file is not enough.

If the failure returns after log rotation or after a new daily file is created, fix the rule that creates future files instead of repeatedly repairing the current file.

chmod 777 Removes the Symptom but Destroys the Access Model

The quickest temptation is:

BASH - Do not use this as the fix
chmod -R 777 storage

The error may disappear because everyone can now write.

But the security boundary disappears with it.

  • Any local process with access to the system receives more permissions than required.
  • It becomes harder to understand which processes are actually supposed to write.
  • The next permission problem is hidden instead of fixed.
  • Production starts depending on unnecessarily broad filesystem permissions.

The correct solution for a shared directory is not to allow everyone to write, but to define the specific group of processes that should be allowed to write.

First I Normalize Existing Log Permissions

BASH - Fix ownership and existing log permissions
cd /var/www/icanup sudo chown -R deploy:www-data storage/logs sudo find storage/logs \  -type d \  -exec chmod 2775 {} \; sudo find storage/logs \  -type f \  -exec chmod 0664 {} \;

For files, the resulting mode is 0664:

Expected file permissions
-rw-rw-r-- deploy:www-data laravel-YYYY-MM-DD.log

Now both the owner deploy and members of the www-data group have write permission.

The Important Part Is setgid on the Directory

Mode 2775 on a directory includes the setgid bit.

For a shared directory, this matters because newly created files and subdirectories inherit the directory group instead of depending only on the creating process primary group.

BASH - Keep the shared group through setgid
sudo chown deploy:www-data storage/logssudo chmod 2775 storage/logs stat -c '%A %a %U:%G %n' storage/logs
Directory with setgid
drwxrwsr-x 2775 deploy:www-data storage/logs

The s in the group permission position indicates that setgid is active.

This solves group inheritance, but it does not by itself guarantee that every new file will be group-writable.

New Files Also Need to Be Created with 0664

For Laravel's daily channel, I want the file permission for new logs to be explicit.

The logging channel can define that permission directly.

PHP - Define permissions for new daily logs
'daily' => [    'driver' => 'daily',    'path' => storage_path('logs/laravel.log'),    'level' => env('LOG_LEVEL', 'debug'),    'days' => 14,    'permission' => 0664,    'locking' => true,],

The exact channel configuration depends on the application. The important contract is that a new file should not become writable only by whichever system user happened to create it first.

umask Complements the Contract

In addition to Laravel configuration, the umask of processes creating shared files matters.

For a deployment shell, 0002 allows group-shared files to retain group write permission.

BASH - Compatible umask for shared files
umask 0002

This matters when deployment creates more than Laravel logs and those files are later used by other processes in the same group.

I do not use umask 0000. There is no reason to make the files writable by everyone.

I Verify Access as Both Users

After changing permissions, looking at ls -l is not enough. I want to verify access as the actual production users.

BASH - Verify the directory as deploy and www-data
cd /var/www/icanup sudo -u deploy \  test -w storage/logs \  && echo 'deploy can write' sudo -u www-data \  test -w storage/logs \  && echo 'www-data can write'

If a daily log already exists, I check that file separately too:

BASH - Verify write permission on the current daily log
LOG_FILE="$(ls -1t storage/logs/laravel-*.log | head -1)" sudo -u deploy \  test -w "$LOG_FILE" \  && echo 'deploy can write the log' sudo -u www-data \  test -w "$LOG_FILE" \  && echo 'www-data can write the log'

It Is Also Useful to Inspect the Entire Path

A correctly configured file still cannot be reached if one of its parent directories blocks traversal for the process.

BASH - Inspect permissions along the full path
namei -l \  /var/www/icanup/storage/logs/laravel-YYYY-MM-DD.log

After the Fix, I Verify the Next New File

This is the most important part of verification.

If I repaired only the current file, the problem can return the next day. The fix is proven only when the next new daily log automatically receives the correct group and mode 0664.

What to verify

Expected result

Owner

May depend on which process created the file

Group

www-data or another explicitly shared group

Mode

0664

Directory

setgid is active

PHP-FPM

Can append to the file

Deployment / CLI

Can also append to the file

This Is a Production Contract, Not a One-Time Server Command

After this incident, I stopped treating storage permissions as a one-time setup step performed during the first deployment.

They are part of the production contract between multiple processes.

  • deploy has explicitly defined permissions.
  • www-data has explicitly defined permissions.
  • Shared writable directories use a shared group.
  • Directories use setgid where group inheritance is required.
  • New files are created with group write permission.
  • Deployment does not repair permission problems with 777.
  • Verification covers future files, not only current ones.

This is also why I prefer production deployment to be reproducible instead of becoming a collection of manual server fixes. I described the broader process in my article about a real Laravel CI/CD pipeline.

What I Do Not Do

  • Do not use chmod -R 777 storage as a permanent fix.
  • Do not repair one current log file and consider the problem solved.
  • Do not run PHP-FPM as root to bypass permission problems.
  • Do not run application commands through sudo without considering which files they create.
  • Do not rely on the correct group without group write permission.
  • Do not rely on 0664 alone if future files can inherit the wrong group.
  • Do not test access only as my own SSH user.

My Short Production Checklist for Laravel Logs

  • Identify every system user that can write Laravel logs.
  • Inspect owner, group, and mode with stat.
  • Verify the PHP-FPM user.
  • Use a shared group for processes that genuinely need write access.
  • Set setgid on the shared writable directory.
  • Use 0664 for log files when the group needs write access.
  • Verify Laravel daily channel permissions.
  • Verify a compatible umask for processes creating shared files.
  • Test the directory and file as both deploy and www-data.
  • Repeat the verification after a new daily log has been created.

I consider a permission problem fixed not when the current file becomes writable again, but when the next file is created correctly without manual intervention.

Conclusion

The Laravel log failure in ICanUp production turned out to be a classic conflict between multiple system users.

deploy could create the new daily log first. The file had the www-data group, but mode 0644 gave that group read permission only. When PHP-FPM running as www-data tried to append the next message, it received Permission denied.

A one-time chmod only repairs the current file. 777 simply removes the access boundary.

The stable fix combines several pieces: a shared group, setgid on the directory, 0664 for log files, a compatible umask, and verification from each real production user.

My main lesson is that permissions for shared runtime files need to be designed as explicitly as database credentials, queue workers, or deployment flow.