Короткий ответ

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 systemmount roread-only
No space leftдиск/inodeдиск, inode
Operation not permitted на chmodimmutable/capabilityэтот материал, lsattr
nginx 403 при 644может быть www-data и AppArmorниже

Возможные причины

  1. Нет execute на каталоге пути.
  2. Файл 640 root:root, сервис User=www-data.
  3. ACL mask режет эффективные права, ls -l врёт плюсиком +.
  4. chattr +i.
  5. AppArmor DENIED в journal.
  6. NFS root_squash, UID 1000 локально ≠ UID на NAS.
  7. PrivateTmp/ProtectHome у systemd — unit.
  8. Домашний /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 домашних и .sshhardening.
  • Документировать 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.