Laravel може працювати без проблем увесь день, а після зміни дати раптом отримати Permission denied під час запису в storage/logs.
У production ICanUp причина виявилася не в самому logging-механізмі Laravel. Причина була на рівні Linux: один і той самий каталог використовували процеси від різних системних користувачів.
Deployment і CLI-команди могли працювати від deploy, а HTTP-запити через PHP-FPM виконувалися від www-data. Якщо саме deploy першим створював новий щоденний файл журналу з правами 0644, www-data пізніше вже не мав права дописувати в нього.

Симптом виглядав як проблема Laravel
На рівні застосунку проблема проявляється дуже просто: Laravel намагається дописати повідомлення у щоденний файл і не може відкрити його в режимі запису.
storage/logs/laravel-YYYY-MM-DD.log Permission deniedCould not be opened in append modeЯкщо дивитися лише на Laravel exception, легко почати перевіряти Monolog, конфігурацію каналу або сам код logging.
Але перша корисна перевірка тут значно простіша: хто є власником файла і які в нього права?
Перша перевірка - власник, група і режим доступу
cd /var/www/icanup stat -c '%A %a %U:%G %n' \ storage/logs \ storage/logs/laravel-*.logПроблемний файл концептуально виглядав так:
-rw-r--r-- 644 deploy:www-data storage/logs/laravel-YYYY-MM-DD.logНа перший погляд група правильна: www-data.
Але 0644 означає, що записувати може лише власник файла. Група має тільки читання.
Чому deploy може писати, а www-data вже ні
Процес | Системний користувач | Що він робить |
|---|---|---|
Deployment та Artisan CLI |
| Оновлює застосунок, виконує Artisan-команди і теж може створювати log-файли |
PHP-FPM |
| Обробляє HTTP-запити і записує application logs |
Queue worker | Залежить від production-конфігурації | Також може записувати в ті самі журнали |
Проблема не в тому, що один із цих користувачів "неправильний". Обидва мають нормальну роль.
Проблема виникає, коли спільний ресурс не має чітко визначеного контракту прав доступу.
0644 пояснює проблему майже повністю
Розряд | Права |
|---|---|
Власник |
|
Група |
|
Інші |
|
Для файла deploy:www-data із правами 0644 користувач deploy може писати.
PHP-FPM від www-data потрапляє в права групи, а там запису немає.
Саме тому правильна група без права w проблему не вирішує.
Чому проблема могла зʼявлятися тільки після створення нового daily log
Laravel daily channel створює новий файл для нового дня.
Старий файл міг мати правильні права після ручного виправлення. Але наступного дня першим до logging міг звернутися deployment або Artisan-процес від deploy, і новий файл знову створювався з іншим режимом доступу.
Тому одноразово виправити вже існуючий файл недостатньо.
chmod 777 прибирає симптом, але руйнує модель доступу
Найшвидша спокуса в такій ситуації:
chmod -R 777 storageПісля цього проблема справді може зникнути, тому що запис дозволений усім.
Але разом із проблемою зникає і сама межа доступу.
- Будь-який локальний користувач або process із доступом до системи отримує зайві права.
- Стає складніше зрозуміти, хто насправді повинен мати право на запис.
- Наступна permission-проблема маскується замість того, щоб бути виправленою.
- Production-конфігурація починає залежати від надмірно широких прав.
Правильне рішення для спільного каталогу - не дозволити запис усім, а визначити конкретну групу процесів, які мають право записувати.
Спочатку я вирівнюю права вже існуючих logs
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 {} \;Для файлів результатом є 0664:
-rw-rw-r-- deploy:www-data laravel-YYYY-MM-DD.logТепер і власник deploy, і члени групи www-data мають право на запис.
Але ключова частина рішення - setgid на каталозі
Права 2775 на каталозі містять setgid bit.
Для спільного каталогу це важливо: нові файли та підкаталоги успадковують групу каталогу замість випадкової primary group процесу, який їх створив.
sudo chown deploy:www-data storage/logssudo chmod 2775 storage/logs stat -c '%A %a %U:%G %n' storage/logsdrwxrwsr-x 2775 deploy:www-data storage/logsЛітера s у групових правах означає, що setgid активний.
Це вирішує питання успадкування групи, але ще не гарантує групі право на запис у кожен новий файл.
Новий файл теж повинен створюватися з 0664
Для Laravel daily channel я хочу чітко визначити permission для нових log-файлів.
У конфігурації logging це можна зробити на рівні самого каналу.
'daily' => [ 'driver' => 'daily', 'path' => storage_path('logs/laravel.log'), 'level' => env('LOG_LEVEL', 'debug'), 'days' => 14, 'permission' => 0664, 'locking' => true,],Точний склад channel configuration залежить від проєкту. Для цієї проблеми важливий сам контракт: новий файл не повинен випадково ставати доступним для запису лише тому користувачу, який створив його першим.
umask доповнює цей контракт
Окрім Laravel configuration, важливо враховувати umask процесів, які створюють спільні файли.
Для deployment shell значення 0002 дозволяє новим group-shared файлам залишати право запису для групи.
umask 0002Це особливо важливо, якщо deployment створює не тільки Laravel logs, а й інші файли, які пізніше повинні використовуватися процесами тієї самої групи.
Я не використовую umask 0000. Мені не потрібно давати право на запис усім.
Я перевіряю права від обох користувачів
Після зміни прав недостатньо подивитися на ls -l. Я хочу перевірити доступ так, як його бачать реальні production-процеси.
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'Якщо щоденний файл уже існує, перевіряю і його окремо:
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'Корисно також перевірити весь шлях до файла
Навіть правильний файл не допоможе, якщо один із батьківських каталогів не дає процесу пройти до нього.
namei -l \ /var/www/icanup/storage/logs/laravel-YYYY-MM-DD.logПісля виправлення я перевіряю саме наступний новий файл
Це найважливіша частина перевірки.
Якщо я виправила лише поточний файл, проблема може повернутися наступного дня. Тому правильний результат підтверджується лише тоді, коли новий daily log автоматично отримує правильну групу і режим 0664.
Що перевірити | Очікуваний результат |
|---|---|
Власник | Може залежати від того, який process створив файл |
Група |
|
Права |
|
Каталог | setgid активний |
PHP-FPM | Може дописати у файл |
Deployment / CLI | Також може дописати у файл |
Це частина production-контракту, а не одноразова серверна команда
Після цього випадку я перестала сприймати права на storage як разове налаштування під час першого deployment.
Це частина production-контракту між кількома процесами.
deployмає чітко визначені права.www-dataмає чітко визначені права.- Спільні writable-каталоги мають спільну групу.
- Каталоги використовують setgid там, де потрібне успадкування групи.
- Нові файли створюються з group-write permission.
- Deployment не виправляє проблему через
777. - Перевірка охоплює не лише поточні, а й майбутні файли.
Це та сама причина, через яку я намагаюся робити production deployment відтворюваним, а не набором ручних виправлень на сервері. Загальніший процес я описувала у матеріалі про реальний Laravel CI/CD pipeline.
Що я не роблю
- Не використовую
chmod -R 777 storageяк постійне рішення. - Не виправляю лише один поточний log-файл і не вважаю задачу завершеною.
- Не змінюю PHP-FPM на
rootзаради обходу permission problem. - Не запускаю application-команди через
sudoбез розуміння, які файли вони створять. - Не покладаюся лише на правильну групу без group-write permission.
- Не покладаюся лише на
0664, якщо майбутні файли можуть успадкувати неправильну групу. - Не перевіряю доступ тільки від свого SSH-користувача.
Мій короткий checklist для Laravel logs у production
- Визначити всіх системних користувачів, які можуть записувати Laravel logs.
- Перевірити owner, group і mode через
stat. - Перевірити PHP-FPM user.
- Використати спільну групу для процесів, яким справді потрібен запис.
- Встановити setgid на shared writable directory.
- Для log-файлів використовувати
0664, якщо групі потрібен запис. - Перевірити Laravel daily channel permission.
- Перевірити сумісний
umaskпроцесів, які створюють спільні файли. - Перевірити каталог і файл від
deployтаwww-dataокремо. - Повторити перевірку після створення нового daily log.
Permission problem вважаю виправленою не тоді, коли поточний файл знову writable, а тоді, коли наступний файл автоматично створюється правильно.
Висновок
Проблема Laravel logs у production ICanUp виявилася типовим конфліктом між кількома системними користувачами.
deploy міг першим створити новий щоденний log. Файл отримував групу www-data, але режим 0644 давав цій групі лише читання. Коли PHP-FPM від www-data намагався записати наступне повідомлення, він отримував Permission denied.
Одноразовий chmod лікує лише поточний файл. 777 просто прибирає межі доступу.
Стабільне рішення складається з кількох частин: спільна група, setgid для каталогу, 0664 для log-файлів, сумісний umask і перевірка доступу від кожного реального production-користувача.
Головний урок для мене: права доступу до shared runtime files потрібно проєктувати так само явно, як database credentials, queue workers або deployment flow.



