Le ticket dit « Permission denied » sur storage/logs. La réponse rapide : chmod -R 777 storage. L'app refonctionne. Trois mois après, un .php déposé dans storage/uploads s'exécute parce que nginx servait encore ce répertoire.
Les permissions web ne sont pas de la bureaucratie Unix : séparation entre code, données, uploads et exécution.
Modèle recommandé
Code (git deploy) : propriétaire humain/CI, lecture seule web. Données (storage, cache) : écriture www-data ciblée. Uploads : sous-arbre sans interpréteur.
| Chemin | Owner | Mode | Note |
|---|---|---|---|
| app/ | deploy:www-data | 750/640 | pas d'upload |
| storage/ | www-data | 770 dirs | logs, cache |
| public/uploads | www-data | 750 | noexec si possible |
umask et systemd
umask 027 ou 002 selon modèle groupe. PHP-FPM user=www-data, pool distinct par app multi-tenant.
Évitez de lancer deploy scripts en root sans chown final.
nginx / Apache
location ^~ /uploads/ { location ~ \.php$ { deny all; } } — bloquer exécution même si fichier .php uploadé.
Separate vhost user si hébergement mutualisé contraint.
ACL pour cas tordus
setfacl -m u:www-data:rwX storage sans ouvrir world. Réinitialisez ACL après restore backup — souvent oublié.
Audit automatisé
Script post-deploy : fail si find . -perm -0002. Alertes sur SUID nouveau. Documentez exception temporaire avec ticket et date expiry.
Audit permissions express
find /var/www -type f -perm -0002
find /var/www -type d -perm -0002
find /var/www -name '*.php' -path '*/uploads/*'
Corriger : chown deploy:www-data, chmod 640 files / 750 dirs, nginx deny PHP uploads.
Documenter exceptions avec ticket et date expiry. Re-scan post-deploy CI.
Formation dev : « permission denied » → strace/id/groups avant chmod.
Un seul répertoire upload world-writable suffit pour compromission chaîne supply petit CMS.
SELinux/AppArmor sur distros hardened : chcon parfois requis après deploy — documentez contextes, pas seulement chmod.
Conteneurs : volume mount UID mapping — permission denied mystérieux souvent UID host ≠ container user.
Dette technique permissions
Auditez héritage : ancien prestataire chmod 777 storage documenté nulle part. Plan remédiation sprint : nginx deny + ACL + chown.
PHP-FPM pool user par client multi-tenant si mutualisé custom — isolation par Unix user > chmod world.
Git ne track pas permissions par défaut — post-checkout hook restore modes si nécessaire.
Synthèse opérationnelle
Les permissions correctes sur un hébergement web séparent lecture, écriture et exécution par rôle : deploy écrit le code, www-data écrit uploads et cache, personne n'exécute PHP dans uploads. Le mutualisé compense parfois par open_basedir ; le VPS exige discipline explicite.
Audit post-incident webshell : chmod seul insuffisant — nginx deny execution, rotation secrets, analyse vector upload. Intégrez scan permissions en CI deploy. Formation : permission denied → owner/groupe avant mode.
Guide pas à pas
- Identifiez user PHP-FPM (
ps aux | grep php-fpm). - Listez owner actuel web root (
namei -l /var/www/app/public). - Appliquez deploy:www-data 750/640 sur code.
- storage et uploads 770/750 www-data.
- nginx deny PHP dans uploads.
- Scan CI world-writable.
- Documentez exceptions.
Sur incident upload malveillant : isolate vhost, preserve fichier pour analyse, rotate DB password si inject SQL suspected, restore permissions from IaC not manual chmod panic.
Tenez runbook daté, métriques avant/après, revue post-incident — la discipline cumulative évite la panique du vendredi soir.
Tenez runbook daté, métriques avant/après, revue post-incident — la discipline cumulative évite la panique du vendredi soir.
Tenez runbook daté, métriques avant/après, revue post-incident — la discipline cumulative évite la panique du vendredi soir.
Tenez runbook daté, métriques avant/après, revue post-incident — la discipline cumulative évite la panique du vendredi soir.
Tenez runbook daté, métriques avant/après, revue post-incident — la discipline cumulative évite la panique du vendredi soir.
Tenez runbook daté, métriques avant/après, revue post-incident — la discipline cumulative évite la panique du vendredi soir.
CMS
WP uploads non-exéc, Laravel storage writable seulement. Symlink deploy permissions on current.
Décider et avancer sans angle mort
- Identifier l'utilisateur PHP-FPM —
namei -lsur la web root, propriétaire deploy:www-data. - Appliquer 750/640 sur le code — storage et uploads 770/750, nginx deny PHP dans uploads.
- Scanner les world-writable — find en CI post-deploy, exceptions ticketées avec date d'expiry.
- Former les devs — permission denied → strace/id/groups avant chmod 777 panique.
- Restaurer depuis IaC après incident upload malveillant — pas de chmod manuel en urgence.
Mutualisé vs VPS : comparez les limites via l'annuaire et nos guides sécurité.
Questions fréquentes
chmod 755 ou 775 pour un site web ?
755 sur fichiers statiques si www-data lit seul. 775 seulement si plusieurs users du groupe doivent écrire — groupe strict, jamais world-writable.
Propriétaire www-data ou deploy ?
Fichiers app : deploy:www-data 640, dossiers 750. Uploads : www-data writable, exécution PHP désactivée dans upload dir via nginx.
ACL vs chmod récursif ?
setfacl pour cas précis (deploy + www-data). chmod -R 777 est un anti-pattern qui masque le vrai conflit user/groupe.
Comment auditer ?
find world-writable, find SUID inattendu, vérifier upload paths. Intégrez dans CI ou script post-deploy.
Prochaine erreur permission denied : changez owner avant mode — dans cet ordre, toujours.
