Короткий ответ
Permission denied — это не одна причина. Разберите путь компонентами: namei -l /полный/путь. Часто закрыт родительский каталог (x для traverse), а не сам файл. Затем владелец/группа, POSIX ACL (getfacl), lsattr (immutable), read-only mount, AppArmor. На Ubuntu Server SELinux обычно не enforcing; MAC — AppArmor. chmod 777 -R маскирует баг и ломает SSH (authorized_keys не должен быть world-writable).
Пользователь admin на host.example. Команды от имени того UID, который реально получает отказ.
Симптомы и как отличить
- пользовательский
cat /etc/app/app.yaml→ denied, root читает; - сервис в journal
Permission deniedнаExecStart; scpпишет denied в домашний каталог;sudo«is not in the sudoers» — это не EACCES файла данных.
| Ошибка | Не права UNIX | Статья |
|---|---|---|
| Read-only file system | mount ro | read-only |
| No space left | диск/inode | диск, inode |
| Operation not permitted на chmod | immutable/capability | этот материал, lsattr |
| nginx 403 при 644 | может быть www-data и AppArmor | ниже |
Возможные причины
- Нет execute на каталоге пути.
- Файл 640
root:root, сервисUser=www-data. - ACL
maskрежет эффективные права,ls -lврёт плюсиком+. chattr +i.- AppArmor
DENIEDв journal. - NFS root_squash, UID 1000 локально ≠ UID на NAS.
- PrivateTmp/ProtectHome у systemd — unit.
- Домашний
/homeне смонтировался — fstab.
Диагностика
1. Путь целиком
namei -l /var/www/app/public/index.html
id www-data
sudo -u www-data -H test -r /var/www/app/public/index.html; echo $?namei показывает mode каждого компонента. Ищите каталог без x для other/group.
2. Стандартные биты и ACL
ls -ld /var/www /var/www/app /var/www/app/public
getfacl /var/www/app/public
getfacl -n /var/www/app/public/index.htmlЕсли есть mask::r--, group ACL не даст execute даже при g+x в уме.
3. Immutable и mount
lsattr /var/www/app/public/index.html
findmnt -T /var/www
mount | grep /var/wwwОпции ro,nosuid,noexec на /tmp ломают скрипты с exec из tmp.
4. AppArmor (SELinux на Ubuntu редко)
sudo aa-status
sudo dmesg -T | grep -i apparmor | tail
sudo journalctl -b | grep -i 'apparmor.*DENIED' | tailПрофиль nginx может запретить /home/admin/site даже при 755.
Проверка SELinux на всякий случай:
getenforce 2>/dev/null || echo 'no selinux'Ожидание на Ubuntu: команды нет или Disabled. Не ставьте selinux=0 в GRUB «на всякий».
5. strace точечно
sudo -u www-data strace -e openat,access,stat -o /tmp/eacces.strace cat /var/www/app/secret.envИщите EACCES. Не оставляйте strace на проде без фильтра.
SSH-ключи: см. также SSH — sshd намеренно отвергает слишком открытые .ssh.
Решение
Сценарий A. Владелец и режим
Минимально:
sudo chown -R www-data:www-data /var/www/app
sudo find /var/www/app -type d -exec chmod 750 {} \;
sudo find /var/www/app -type f -exec chmod 640 {} \;Не 777. Каталоги 750/755, файлы 640/644 по модели.
Сценарий B. ACL
sudo setfacl -m u:www-data:rX /var/www/app
sudo setfacl -R -m u:www-data:rX /var/www/app
sudo getfacl /var/www/appСброс запутавшихся ACL: setfacl -b после понимания, что default ACL больше не нужны.
Сценарий C. Immutable
sudo lsattr /etc/app/app.yaml
sudo chattr -i /etc/app/app.yamlВыясните, кто поставил +i (ansible, ручной hardening).
Сценарий D. AppArmor
Не aa-teardown. Для подтверждения: aa-complain /usr/sbin/nginx на время теста, затем точечный профиль (aa-logprof) и возврат enforce.
Сценарий E. systemd Protect*
Drop-in ReadWritePaths= / BindPaths= вместо снятия ProtectSystem.
NFS, CIFS и контейнеры: EACCES, которого нет в ls -l
На NFS root_squash превращает root клиента в nobody. sudo cat с Ubuntu на NAS даёт EACCES, хотя ls -l показывает root:root 644. Смотрите /etc/exports на стороне NAS и id на клиенте. Лечение — правильный UID приложения, не chmod на mount.
CIFS: опции uid=admin,gid=admin,file_mode=0640. POSIX chown на cifs часто no-op или EPERM.
В Docker bind-mount UID 33 (www-data в Debian) может не совпасть с www-data хоста после смены образа. namei на хосте врёт относительно процессов в user namespace.
systemd-run -p ProtectHome=read-only для отладки воспроизведёт отказ сервиса без правки vendor unit. Если так EACCES пропадает при ProtectHome=false — это не chmod файлов, а namespace.
AppArmor на 24.04 для rsyslog и named строже в некоторых профилях. journalctl -t apparmor важнее ls -l. Перевод профиля в complain — на час диагностики, затем aa-enforce.
Отдельно проверьте sticky/bit на каталогах обмена (/tmp 1777): создание файла ок, удаление чужого — EPERM, это не «сломались права приложения». lsattr -d на каталог покажет t для tmp. Не снимайте sticky, чтобы «скрипт мог чистить всё».
Как проверить, что проблема устранена
От того же UID:
sudo -u www-data test -r /path && echo readable
sudo -u www-data test -x /path/to/dir && echo traversable
sudo systemctl restart app.service
journalctl -u app.service -n 20 --no-pagerПовторите исходную операцию (curl локально, sudo скрипт от admin).
Если не помогло
- Только после reboot ломается: fstab noexec/nosuid.
- NFS:
idна клиенте vs сервере, squashing. - Контейнер: user namespace, не хостовые UID.
- Capability
CAP_DAC_OVERRIDEу бинаря — не чините chmod, смотрите, зачем SUID.
Профилактика
- Деплой от отдельного пользователя, не от root с последующим 777.
- В baseline запрет world-writable домашних и
.ssh— hardening. - Документировать ACL, не копить маски.
- AppArmor в enforce, жалобы — в профили, не в disable.
FAQ
Почему root тоже получает Permission denied?
Чаще ro mount, immutable, seccomp, AppArmor. Root не обходит MAC и +i.
chmod +x на файл есть, запуск denied?
Нет x на каталоге, noexec, или интерпретатор из shebang недоступен (тогда 203/EXEC у systemd).
Нужен ли umask 000?
Нет. Это фабрика 666/777.
getfacl пустой, плюс в ls есть
Перечитайте ls -l — + значит ACL есть. getfacl без sudo на чужой файл может не показать всё.
Windows ACL на SMB share
Это не POSIX. Смотрите cifs uid=/gid=/file_mode, не chmod на mount.