Certaines pannes ne crient pas. Elles chuchotent. Un discret «403 Forbidden» dans votre navigateur, un ticket de support avec une capture d’écran, et un développeur jurant qu’il «n’a rien touché». Pendant ce temps, le serveur web se tient à la porte avec un clipboard et refuse l’entrée parce que vous avez oublié un seul bit d’exécution sur un répertoire parent.
Si votre réflexe correctif est chmod -R 777, retirez vos mains du clavier. On peut résoudre les 403 sur Debian/Ubuntu proprement, de façon prévisible, et sans transformer votre webroot en jardin communautaire.
Guide de diagnostic rapide
Quand vous avez un 403, vous n’êtes pas en train de «déboguer les permissions». Vous localisez le premier endroit où l’utilisateur du processus web ne peut pas traverser ou lire. Faites-le dans cet ordre et vous arrêterez de tâtonner.
1) Confirmez quel est réellement l’utilisateur du processus du serveur web
N’assumez pas www-data. Les valeurs par défaut Debian/Ubuntu le suggèrent souvent, mais conteneurs, durcissement ou overrides systemd peuvent le changer.
2) Identifiez le chemin exact servi
Est-ce le DocumentRoot attendu ? Un lien symbolique ? Un alias ? Un root par vhost ? Une redirection «utile» vers le répertoire personnel de quelqu’un ?
3) Lisez la ligne de log d’erreur correspondant à votre requête
Le 403 a plusieurs variantes : permissions filesystem, index manquant avec listing désactivé, authentification requise, ou modules de politique (AppArmor/SELinux). Le log vous dit laquelle vous avez.
4) Testez l’accès en tant qu’utilisateur du serveur web, depuis le système de fichiers
Si le processus ne peut pas traverser les répertoires parents, peu importe que le fichier lui‑même soit lisible.
5) Après cela seulement, changez les permissions — et faites le minimum nécessaire
Privilégiez l’accès basé sur un groupe ou les ACL. Utilisez correctement le bit d’exécution. Évitez tout répertoire accessible en écriture pour «le monde» dans un webroot, sauf si vous aimez les revues d’incident.
Le modèle de permissions qui cause réellement des 403
403 n’est pas un seul bug ; c’est une catégorie
Dans Apache et Nginx, «403 Forbidden» signifie : «J’ai compris la requête, mais je ne la servirai pas.» Ça peut être parce que :
- Accès au système de fichiers refusé (le plus fréquent).
- L’indexation de répertoire est désactivée et il n’y a pas de fichier index.
- Les règles d’accès refusent la requête (Apache
Require, Nginxdeny). - Échec d’authentification/autorisation (parfois 401, parfois 403 selon la config).
- Le contrôle d’accès obligatoire l’a bloqué (AppArmor ou SELinux).
Cet article porte sur l’angle permissions : bits Unix classiques, propriété, groupes, umask et ACL, et comment tout cela s’imbrique avec les choix de packaging Debian/Ubuntu.
La grosse subtilité : “execute” sur un répertoire signifie traverser
Sur les répertoires :
- read (r) permet de lister les noms.
- write (w) permet de créer/supprimer/renommer des entrées.
- execute (x) permet de traverser le répertoire et d’accéder aux inodes à l’intérieur si vous connaissez déjà le nom.
L’incident classique ressemble à ceci : le fichier est en 644 et semble lisible, mais un répertoire parent est en 750 appartenant à un autre groupe, et le serveur web ne peut pas le traverser. Le navigateur voit 403. L’humain voit «mais le fichier est en 644». Les deux ont techniquement raison, et c’est pourquoi ça revient.
Les symlinks ne sont pas des portails magiques
Les liens symboliques sont juste des pointeurs. Le serveur a besoin de permissions sur le chemin cible, y compris tous les répertoires parents. De plus, Apache peut être configuré pour refuser de suivre les symlinks à moins que ce soit explicitement autorisé.
Pourquoi 777 «fonctionne» et pourquoi c’est un piège
777 donne lecture/écriture/exécution à tout le monde. Pour les répertoires, cela signifie que n’importe quel utilisateur local (ou service compromis) peut déposer ou remplacer des fichiers dans votre webroot. Si le serveur exécute des scripts (PHP, CGI, etc.), vous avez pratiquement installé un panneau «exécutez du code arbitraire ici».
Petite blague : chmod 777, c’est comme laisser vos clés sous le paillasson — sauf que le paillasson est sur la place publique.
Une maxime opérationnelle fiable
Paraphrasant W. Edwards Deming : La plupart des problèmes viennent du système, pas de l’effort individuel.
Les incidents de permissions ne sont généralement pas dus à la «négligence». Ils viennent d’un chemin de déploiement, d’un modèle de propriété et de valeurs par défaut mal conçus.
Faits et contexte intéressants (pourquoi ça revient)
- Les permissions Unix datent d’avant le web. Le modèle a été conçu pour des systèmes multi‑utilisateurs en partage de temps, pas pour servir des fichiers statiques au monde entier.
- Le bit d’exécution sur les répertoires est plus ancien que la plupart de nos carrières. Il signifie «recherche» ou «traverse», et l’oublier est un rite de passage non désiré.
- Debian a popularisé des utilisateurs de service comme
www-data. L’idée est de garder les démons non‑privilégiés et cohérents entre installations. - L’histoire de sécurité d’Apache a influencé les defaults d’aujourd’hui. Des options comme
Options FollowSymLinksetRequire all grantedont évolué parce que «servir tout ce qui est lisible» était une mauvaise idée. - Les ACL POSIX n’ont pas toujours été courantes. Elles sont devenues mainstream quand les équipes ont eu besoin d’un partage plus fin qu’un seul groupe.
- Umask est un moteur de politique discret. Ce n’est pas une permission ; c’est la soustraction par défaut appliquée à la création de fichiers. Elle peut ruiner votre plan pendant que vous dormez.
- Beaucoup d’incidents de “permissions” sont en fait des refus par un module de politique. AppArmor (Ubuntu) et SELinux (certains Debian) peuvent bloquer l’accès même quand les bits mode semblent corrects.
- Les piles web ont adopté le principe du moindre privilège progressivement. Exécuter des serveurs en root était courant ; maintenant c’est un signal rouge. Le modèle de permissions de votre déploiement doit refléter cette réalité.
Tâches pratiques : commandes, sorties et décisions
Ci‑dessous des vérifications pratiques à lancer sur Debian/Ubuntu. Chaque tâche inclut : la commande, ce que signifie la sortie, et la décision à en tirer. Ce sont les manœuvres qui arrêtent les 403 sans créer un cratère de sécurité.
Task 1: Confirm which web server is running and its process user
cr0x@server:~$ ps -eo user,comm,args | egrep 'apache2|nginx' | head
root nginx: master process /usr/sbin/nginx -g daemon on; master_process on;
www-data nginx: worker process
root /usr/sbin/apache2 -k start
www-data /usr/sbin/apache2 -k start
Ce que ça signifie : Le maître peut être root, les workers devraient être non‑privilégiés (souvent www-data). Cet utilisateur non‑privilégié doit pouvoir lire/traverser le web root.
Décision : Utilisez l’utilisateur worker (www-data ici) pour tous les tests d’accès et pour concevoir la propriété/groupe.
Task 2: Identify the DocumentRoot or web root path actually in use
cr0x@server:~$ apache2ctl -S 2>/dev/null | sed -n '1,25p'
VirtualHost configuration:
*:80 example.local (/etc/apache2/sites-enabled/000-default.conf:1)
ServerRoot: "/etc/apache2"
Main DocumentRoot: "/var/www/html"
Ce que ça signifie : La config vhost effective d’Apache pointe vers un DocumentRoot spécifique. Si vous modifiez un autre répertoire, vous déboguez la mauvaise chose.
Décision : Assurez‑vous que vos permissions fonctionnent sur le chemin réellement servi par Apache/Nginx, pas sur celui que vous souhaiteriez servir.
Task 3: For Nginx, list server blocks and roots
cr0x@server:~$ sudo nginx -T 2>/dev/null | egrep -n 'server_name|root ' | head -n 12
34: server_name example.local;
41: root /srv/www/example/current/public;
78: server_name static.example.local;
82: root /srv/www/static;
Ce que ça signifie : Nginx peut avoir plusieurs roots. Un 403 sur un nom d’hôte peut être un problème de permissions sur un autre arbre de répertoires.
Décision : Ciblez le bon root pour le vhost en échec.
Task 4: Read the error log for the matching denial
cr0x@server:~$ sudo tail -n 20 /var/log/nginx/error.log
2025/12/30 10:31:42 [error] 19214#19214: *55 open() "/srv/www/example/current/public/index.html" failed (13: Permission denied), client: 10.10.10.23, server: example.local, request: "GET / HTTP/1.1", host: "example.local"
Ce que ça signifie : (13: Permission denied) indique des permissions filesystem (ou une politique MAC). Ce n’est pas une directive Nginx deny ni un index manquant.
Décision : Passez aux vérifications au niveau filesystem (mode bits, propriété, ACL, AppArmor/SELinux).
Task 5: Check permissions along the entire path (“namei” is your friend)
cr0x@server:~$ namei -l /srv/www/example/current/public/index.html
f: /srv/www/example/current/public/index.html
drwxr-xr-x root root /
drwxr-xr-x root root srv
drwxr-x--- deploy deploy www
drwxr-xr-x deploy deploy example
drwxr-xr-x deploy deploy current
drwxr-xr-x deploy deploy public
-rw-r--r-- deploy deploy index.html
Ce que ça signifie : Le problème est visible : /srv/www est drwxr-x--- appartenant à deploy:deploy. L’utilisateur du serveur web ne peut pas le traverser parce que «other» n’a pas le bit d’exécution et qu’il n’est pas dans le groupe.
Décision : Corrigez la traverse sur les répertoires parents, idéalement via l’adhésion à un groupe ou des ACL — pas en ouvrant tout au monde.
Task 6: Test access as the web server user (the most honest test)
cr0x@server:~$ sudo -u www-data -s -- bash -lc 'test -r /srv/www/example/current/public/index.html && echo READ_OK || echo READ_NO; test -x /srv/www && echo TRAVERSE_OK || echo TRAVERSE_NO'
READ_NO
TRAVERSE_NO
Ce que ça signifie : L’utilisateur worker ne peut pas traverser /srv/www, donc il ne peut rien lire en dessous.
Décision : Choisissez une stratégie de permissions (groupe, ACL, ou propriété dédiée du web-root) et appliquez‑la de manière cohérente.
Task 7: Check ownership and groups, including whether www-data is in the expected group
cr0x@server:~$ id www-data
uid=33(www-data) gid=33(www-data) groups=33(www-data)
Ce que ça signifie : www-data n’a pas de groupes additionnels. Si vous prévoyez d’utiliser un groupe partagé comme web ou deploy, vous devez l’ajouter et recharger les services si nécessaire.
Décision : Décidez d’ajouter www-data à un groupe (pratique courante) ou d’utiliser des ACL (plus propre quand plusieurs équipes partagent des chemins).
Task 8: Inspect the directory mode bits and spot missing execute bits
cr0x@server:~$ stat -c '%A %U:%G %n' /srv/www /srv/www/example /srv/www/example/current/public
drwxr-x--- deploy:deploy /srv/www
drwxr-xr-x deploy:deploy /srv/www/example
drwxr-xr-x deploy:deploy /srv/www/example/current/public
Ce que ça signifie : Le répertoire de haut niveau est le point d’étranglement. Votre serveur web n’a pas besoin d’y écrire, mais il a besoin d’exécution (traverse) et de lecture sur les fichiers servis.
Décision : Ajustez le minimum requis : souvent 751 ou un modèle basé groupe 750 selon votre stratégie.
Task 9: Check if ACLs are in play (and whether defaults are set)
cr0x@server:~$ getfacl -p /srv/www/example/current/public | sed -n '1,30p'
# file: /srv/www/example/current/public
# owner: deploy
# group: deploy
user::rwx
group::r-x
other::r-x
Ce que ça signifie : Pas d’ACL spécifiques ; seuls les bits classiques s’appliquent. Si vous attendiez une ACL qui donne l’accès à www-data, elle n’est pas présente.
Décision : Si vous optez pour la stratégie ACL, appliquez‑la au bon niveau et définissez des ACL par défaut afin que les nouveaux fichiers héritent de l’accès.
Task 10: Detect “umask sabotage” in your deployment user environment
cr0x@server:~$ sudo -u deploy -s -- bash -lc 'umask; touch /tmp/umask-test-file; stat -c "%A %n" /tmp/umask-test-file; rm -f /tmp/umask-test-file'
0027
-rw-r----- /tmp/umask-test-file
Ce que ça signifie : Un 0027 de umask crée des fichiers non lisibles par le monde et non modifiables par le groupe. Si votre serveur dépend de la lisibilité par «other», vous aurez des 403 intermittents selon la manière dont les fichiers ont été créés.
Décision : Alignez le umask avec votre stratégie de permissions. Pour le partage de groupe : souvent 0002 est raisonnable. Pour des configurations plus strictes : restez restrictif et utilisez des ACL.
Task 11: Check for Apache config-level denial that looks like a permissions issue
cr0x@server:~$ sudo apache2ctl -t -D DUMP_RUN_CFG 2>/dev/null | egrep -n 'User|Group|ServerRoot|DocumentRoot' | head
3:User: name="www-data" id=33
4:Group: name="www-data" id=33
16:ServerRoot: "/etc/apache2"
22:DocumentRoot: "/var/www/html"
Ce que ça signifie : Vous voyez l’utilisateur/groupe d’exécution et le DocumentRoot. Si votre vhost pointe ailleurs, confirmez avec apache2ctl -S. Si le DocumentRoot est correct mais que vous servez depuis /home via un alias, vous avez volontairement créé un casse‑tête de permissions.
Décision : Si la config et le filesystem sont en désaccord, corrigez la config d’abord. Les permissions doivent refléter l’intention, pas des accidents.
Task 12: For Nginx, check if the worker user is overridden in config
cr0x@server:~$ egrep -n '^\s*user\s+' /etc/nginx/nginx.conf
2:user www-data;
Ce que ça signifie : Confirme quel utilisateur lit les fichiers. Si ce n’est pas www-data, adaptez vos tests et votre plan de permissions en conséquence.
Décision : Gardez l’utilisateur worker stable. Le changer pour «corriger des permissions» est généralement un mauvais signe.
Task 13: Check if the filesystem is mounted with options that can block expected behavior
cr0x@server:~$ findmnt -no TARGET,FSTYPE,OPTIONS /srv
/srv ext4 rw,relatime
Ce que ça signifie : Pour du contenu statique, les options de montage causent rarement des 403, mais des flags comme noexec importent si vous servez des CGI exécutables ou exécutez des artefacts de build en place.
Décision : Si vous comptez exécuter des fichiers sur ce montage, retirez noexec (avec prudence) ou changez d’architecture (mieux : n’exécutez pas depuis le web root).
Task 14: Check AppArmor status and whether it’s likely involved
cr0x@server:~$ sudo aa-status | sed -n '1,18p'
apparmor module is loaded.
24 profiles are loaded.
12 profiles are in enforce mode.
/usr/sbin/nginx
/usr/sbin/apache2
Ce que ça signifie : Si le profil est en mode enforce, il peut refuser l’accès à des chemins hors des emplacements attendus même si les bits Unix sont corrects. Les logs mentionneront les refus AppArmor.
Décision : Si votre DocumentRoot est non conventionnel (par ex. /srv/www), vérifiez que le profil AppArmor l’autorise, ou déplacez le root vers un chemin standard.
Task 15: Confirm the actual effective permissions of a file including ACL masks
cr0x@server:~$ getfacl -p /srv/www/example/current/public/index.html
# file: /srv/www/example/current/public/index.html
# owner: deploy
# group: deploy
user::rw-
group::r--
other::r--
Ce que ça signifie : Ce fichier est lisible par le monde, mais cela n’aide pas si un répertoire parent bloque la traverse. Aussi, si vous ajoutez des ACL plus tard, l’entrée mask peut limiter silencieusement les droits effectifs.
Décision : Réparez d’abord les permissions du chemin ; ensuite assurez‑vous que les fichiers sont lisibles par le principal attendu (groupe ou utilisateur via ACL).
Task 16: Validate that your “fix” didn’t introduce world-writable directories
cr0x@server:~$ sudo find /srv/www/example/current/public -type d -perm -0002 -maxdepth 3 -print | head
Ce que ça signifie : Pas de sortie signifie qu’aucun répertoire world-writable n’a été trouvé (au moins jusqu’à la profondeur max). Une sortie listerait des répertoires que n’importe qui peut écrire.
Décision : Si quelque chose apparaît, retirez l’écriture pour le monde et basculez vers un écriture par groupe ou vers un chemin d’upload contrôlé.
Schémas de permissions recommandés (choisissez‑en un et tenez‑vous y)
Il y a plusieurs manières de rendre un serveur web capable de lire des fichiers. Seules quelques‑unes sont durables. L’objectif : déploiements stables, privilèges minimaux et héritage prévisible. Choisissez un schéma selon qui écrit les fichiers et combien de personnes/outils interviennent.
Pattern A: Comportement Debian classique pour sites statiques simples
Utilisez lorsque : Un administrateur gère le contenu, pas d’utilisateur de déploiement partagé, composants minimaux.
- DocumentRoot :
/var/www/htmlou/var/www/site - Propriété :
root:rootoudeploy:www-dataselon le workflow - Permissions : répertoires
755, fichiers644
Cela fonctionne parce que la lisibilité/traverse pour «other» est autorisée. Ce n’est pas le modèle le plus strict, mais il est stable et difficile à casser accidentellement.
Évitez : rendre le web root modifiable par l’utilisateur du serveur web. Si l’application a besoin d’uploads, créez un répertoire écrivable séparé avec portée limitée et configuration prudente.
Pattern B: Modèle de groupe partagé (recommandé pour la plupart des équipes)
Utilisez lorsque : un utilisateur de déploiement (ou un runner CI) écrit les fichiers ; le serveur web ne fait que lire ; plusieurs opérateurs ont besoin d’accès.
- Créez un groupe :
web - Rendez le DocumentRoot appartenant au groupe
web - Ajoutez
www-dataet les utilisateurs deploy au groupeweb - Activez le bit setgid sur les répertoires pour que le groupe soit hérité
- Utilisez umask
0002pour les processus de déploiement afin que la lisibilité par le groupe soit cohérente
Modes typiques :
- répertoires :
2775(le 2 initial est setgid) - fichiers :
664
C’est le modèle d’entreprise ennuyeux. L’ennui, c’est bien.
Pattern C: Modèle ACL (idéal quand les groupes partagés deviennent confus)
Utilisez lorsque : plusieurs applications partagent des arbres, différentes équipes déploient, ou vous ne pouvez pas compter sur l’appartenance aux groupes (conteneurs, utilisateurs éphémères, runners CI).
- Conservez la propriété avec l’utilisateur deploy ou app
- Accordez lecture+traverse à
www-datavia ACL sur l’arbre de répertoires - Définissez des ACL par défaut pour que les nouveaux fichiers héritent de la règle
Cela évite le «group sprawl», où tout finit dans un méga‑groupe parce que c’est plus simple que de réfléchir.
Pattern D: Répertoires de release immuables (pour la fiabilité)
Utilisez lorsque : vous faites des déploiements atomiques (changement de symlink) et voulez éviter la dérive des permissions.
- Chaque répertoire de release est créé avec le bon propriétaire/mode et n’est jamais modifié.
- Les chemins écrivables (uploads, cache) vivent en dehors de l’arbre de release.
- Un lien symbolique
currentpointe vers la release active.
Il est difficile d’obtenir des 403 à partir d’un arbre que vous ne mutiez jamais. Il est aussi difficile d’obtenir «ça marchait hier» puisque l’arbre d’hier est toujours là et identique.
Deuxième petite blague : Les permissions sont comme la politique de bureau — tout le monde fait semblant de les comprendre jusqu’à ce qu’un bit “execute” soit promu.
Trois mini-récits d’entreprise
1) Incident causé par une mauvaise hypothèse : «www-data peut lire /home, non ?»
Dans une société de taille moyenne, une équipe a lancé un tableau de bord interne temporaire. Il vivait dans le répertoire personnel d’un développeur parce que c’était rapide. Nginx pointait vers /home/alex/dashboard/public. Ça fonctionnait en staging, sur une machine de production, puis plus.
L’échec était classique : 403 sur un seul nœud derrière le load balancer. L’ingénieur en astreinte a comparé les configs Nginx, qui étaient identiques, puis a suspecté le cache, le DNS, puis «peut‑être le LB». Le log d’erreur montrait Permission denied, mais c’était écarté parce que «les fichiers sont en 644».
La différence était un répertoire parent : sur un nœud, /home/alex était 750. Sur un autre, il était 755. Le dashboard «fonctionnait» seulement là où les répertoires personnels étaient traversables par le monde. La sécurité avait récemment durci les permissions par défaut des home via /etc/adduser.conf, et personne n’a relié ce changement à une app web vivant dans un home.
La correction n’a pas été d’assouplir la sécurité des home. La correction a été de déplacer le dashboard vers /srv/www avec un groupe et une propriété prévisibles, puis d’ajuster AppArmor pour le nouveau chemin. Ensuite, le 403 a disparu et est resté disparu.
La leçon : ne basez jamais des web roots de production sur des chemins conçus pour des humains. Les humains se connectent. Les services ne doivent pas dépendre de la politique des home.
2) Optimisation qui s’est retournée contre eux : «Mettons 750 partout»
Une autre organisation, suite à un audit de sécurité, a décidé de durcir les permissions sur les web roots en mettant récursivement des répertoires à 750 et des fichiers à 640, propriété deploy:deploy. L’idée était sensée : ne pas laisser «other» lire le code.
Ils ont exécuté un chmod récursif dans un pas de déploiement. Ça a passé en test parce que leur système de déploiement exécutait Nginx en tant que deploy (non recommandé, mais ça masquait le problème). En production, Nginx tournait comme www-data, et l’étape de déploiement a supprimé le bit de traverse «other» sur un répertoire parent. La requête suivante a renvoyé 403.
La panique a mené au correctif : chmod -R 755 pour restaurer le service. Le site est revenu, mais toutes les répertoires sont devenus traversables par le monde, annulant l’objectif de l’audit, et créant une divergence entre nœuds parce que toutes les machines n’ont pas été corrigées de la même manière.
La solution durable a été d’implémenter un modèle de groupe partagé avec setgid, et de faire produire par l’outillage de déploiement des artefacts avec permissions group‑read. Ils ont fini avec des réglages 2750/2640 à certains endroits, mais toujours avec le bon groupe et un héritage prévisible.
La leçon : «verrouiller» n’est pas un plan. Le modèle de permissions en est un. Un chmod récursif est une pelle dans une salle de verre.
3) Pratique ennuyeuse mais correcte qui a sauvé la mise : «namei dans le runbook»
Une équipe paiements exécutait plusieurs instances Nginx servant des assets statiques. Rien d’excitant. Puis un déploiement a introduit une nouvelle couche de répertoires : /srv/www/payments/releases/2025-12-30/public. L’app fonctionnait sur la plupart des nœuds, mais un seul renvoyait 403. Le déploiement semblait identique. L’ingénieur en astreinte a ressenti la panique familière : incident intermittent, spécifique à un nœud, juste avant un pic de trafic.
Voici ce qui l’a rendu ennuyeux — en bien. L’équipe avait une étape dans le runbook : «Exécuter namei -l sur le chemin exact du fichier.» Pas de débat, pas de devinette. Ils l’ont exécuté et ont vu instantanément que /srv/www/payments avait le mauvais groupe sur ce nœud, probablement à cause d’un hotfix manuel fait des mois plus tôt.
Ils ont corrigé la propriété de groupe, vérifié le bit setgid, et relancé leur job «vérification de dérive des permissions» qui comparait les modes critiques sur la flotte. Pas de chmod récursif, pas de 777 panique, pas de changement d’utilisateur du serveur web.
Dans le postmortem, l’action la plus précieuse n’était pas un outil. C’était social : maintenir le runbook court, obligatoire et précis. Les procédures ennuyeuses gagnent parce qu’elles empêchent le freestyle à 2h du matin.
Erreurs fréquentes : symptômes → cause → correction
Voici ce que je vois régulièrement sur des flottes Debian/Ubuntu. Les symptômes se ressemblent ; les correctifs diffèrent.
1) Symptom: 403, error log says “Permission denied (13)”
Cause racine : bit d’exécution manquant sur un répertoire parent (pas de traverse).
Correction : assurez‑vous que chaque répertoire du chemin est traversable par le principal du serveur web (groupe ou ACL). Utilisez namei -l pour trouver le premier bloqueur.
2) Symptom: Files are 644, directories are 755, still 403
Cause racine : refus par AppArmor/SELinux, ou configuration Apache qui refuse l’accès.
Correction : vérifiez les logs du noyau / refus AppArmor et les règles Require d’Apache. Ne forcez pas les choses avec chmod contre un moteur de politique.
3) Symptom: 403 only after deploy; fixed by restarting, then returns
Cause racine : le déploiement a créé de nouveaux répertoires/fichiers avec un umask restrictif ou un mauvais groupe ; l’ancien contenu avait les bonnes permissions.
Correction : imposez le umask dans l’environnement de déploiement, activez setgid sur les répertoires, ou utilisez des ACL par défaut pour que le nouveau contenu hérite de l’accès.
4) Symptom: 403 on a symlinked path; direct path works
Cause racine : Apache refuse de suivre les symlinks, ou les permissions du chemin cible diffèrent.
Correction : assurez‑vous que la config autorise les symlinks où prévu (Options FollowSymLinks ou alternatives plus sûres) et que le chemin cible est lisible/traversable.
5) Symptom: 403 on directory URL, file URLs work
Cause racine : index manquant et autoindex désactivé, ou bit d’exécution manquant sur le répertoire.
Correction : ajoutez un fichier index, activez l’indexation volontairement (rare), ou corrigez les permissions du répertoire.
6) Symptom: 403 only on one node in a cluster
Cause racine : dérive des permissions (hotfix manuel), umask différent, appartenance aux groupes différente, ou version de la politique MAC différente.
Correction : comparez les sorties de stat/getfacl entre nœuds, et auditez l’appartenance aux groupes et l’état d’application des politiques.
7) Symptom: Everything works until you add a new team member
Cause racine : les permissions dépendent d’un utilisateur spécifique possédant l’arbre ; pas de stratégie de groupe/ACL partagée.
Correction : passez à la propriété par groupe et aux répertoires setgid, ou aux ACL. Évitez «propriété par qui a déployé en dernier».
8) Symptom: You “fixed” it with 777 and now security is calling
Cause racine : modèle de permissions absent ; le correctif d’urgence est devenu la politique.
Correction : inversez les permissions world‑writable, implémentez une stratégie groupe/ACL, et séparez les répertoires écrivables (uploads/cache) du code en lecture seule.
Listes de contrôle / plan pas à pas
Plan A: Fix a live 403 safely (minimum change, maximum certainty)
- Récupérez l’URL exacte en échec et mappez‑la à un chemin filesystem (depuis la config vhost et les logs).
- Lisez la ligne de log d’erreur pour cette requête ; confirmez s’il s’agit de
(13: Permission denied)ou d’un refus de config. - Exécutez
namei -lsur le chemin exact et identifiez le premier répertoire sans traverse pour l’utilisateur du serveur web. - Faites un test d’accès en tant qu’utilisateur du serveur web avec
sudo -u www-datapour confirmer le mode d’échec. - Appliquez le plus petit changement de permission nécessaire sur le répertoire bloquant (privilégiez l’exécution pour le groupe ou l’ACL).
- Retestez l’accès en tant que l’utilisateur web, puis testez via HTTP.
- Scannez pour détecter des répertoires world‑writable accidentels et revenez en arrière si vous en trouvez.
- Notez ce qui a changé et pourquoi ; si vous ne pouvez pas l’expliquer, vous n’avez pas fixé le problème — vous avez eu de la chance.
Plan B: Standardize a web root for a team (group model)
- Créez un groupe dédié (
web) pour «peut lire le contenu web». - Ajoutez
www-dataet les utilisateurs deploy au groupeweb. - Rendez la propriété du web root à
deploy:web(ouroot:webpour un contrôle des changements plus strict). - Mettez les répertoires en
2775et les fichiers en664là où approprié. - Définissez le umask de déploiement à
0002afin que la lisibilité par le groupe soit constante. - Séparez les répertoires écrivables (uploads, cache) avec des contrôles plus stricts et une configuration explicite de l’app.
- Ajoutez une vérification de dérive : un job périodique ou une règle de gestion de configuration pour vérifier propriété et modes.
Plan C: Standardize with ACLs (when multiple groups are a headache)
- Conservez la propriété avec l’utilisateur deploy/app ; ne luttez pas contre votre organigramme dans
/etc/group. - Appliquez une ACL donnant à
www-datarxsur les répertoires etrsur les fichiers. - Appliquez des ACL par défaut sur les répertoires pour que les nouveaux fichiers héritent de la règle.
- Vérifiez que le mask d’ACL ne réduit pas accidentellement les permissions effectives.
- Documentez la décision ACL dans votre runbook afin que personne ne «nettoie» cela plus tard.
Concrete implementation examples (use one, not all)
Example 1: Shared group model under /srv/www/example
cr0x@server:~$ sudo groupadd -f web
cr0x@server:~$ sudo usermod -aG web www-data
cr0x@server:~$ sudo usermod -aG web deploy
cr0x@server:~$ sudo chown -R deploy:web /srv/www/example
cr0x@server:~$ sudo find /srv/www/example -type d -exec chmod 2775 {} +
cr0x@server:~$ sudo find /srv/www/example -type f -exec chmod 0664 {} +
Ce que ça donne : Ces commandes sont silencieuses en réussite. L’effet : les répertoires héritent maintenant du groupe web et sont traversables par le groupe.
Décision : Si vous utilisez ce modèle, assurez‑vous que les processus de déploiement créent des fichiers lisibles par le groupe ; sinon vous devrez sans cesse «corriger» les permissions après chaque déploiement.
Example 2: ACL model to grant www-data read/traverse without changing groups
cr0x@server:~$ sudo setfacl -R -m u:www-data:rx /srv/www/example/current/public
cr0x@server:~$ sudo find /srv/www/example/current/public -type f -exec setfacl -m u:www-data:r {} +
cr0x@server:~$ sudo setfacl -m d:u:www-data:rx /srv/www/example/current/public
cr0x@server:~$ sudo getfacl -p /srv/www/example/current/public | sed -n '1,25p'
# file: /srv/www/example/current/public
# owner: deploy
# group: deploy
user::rwx
user:www-data:r-x
group::r-x
mask::r-x
other::r-x
default:user::rwx
default:user:www-data:r-x
default:group::r-x
default:mask::r-x
default:other::r-x
Ce que ça signifie : L’ACL accorde à www-data la traverse/lecture. L’ACL par défaut garantit que les nouveaux répertoires héritent de la règle.
Décision : Utilisez les ACL quand la propriété par groupe est politiquement ou opérationnellement instable. Mais engagez‑vous : les modèles mixtes deviennent vite bizarres.
Example 3: Fix only the blocking directory traverse bit (surgical live fix)
cr0x@server:~$ sudo chmod o+x /srv/www
cr0x@server:~$ namei -l /srv/www/example/current/public/index.html | sed -n '1,6p'
f: /srv/www/example/current/public/index.html
drwxr-xr-x root root /
drwxr-xr-x root root srv
drwxr-x--x deploy deploy www
drwxr-xr-x deploy deploy example
Ce que ça signifie : o+x sur le répertoire permet la traverse sans autoriser la liste. Cela peut être un compromis raisonnable si vous ne voulez pas que le monde énumère le contenu des répertoires.
Décision : C’est bien pour une restauration rapide, mais pensez à migrer vers un modèle groupe/ACL pour la stabilité à long terme.
FAQ
1) Why does a missing execute bit on a directory cause 403?
Parce que sans execute sur un répertoire, le processus ne peut pas y entrer — même si le fichier à l’intérieur est lisible. Le serveur web ne peut pas atteindre l’inode, donc il échoue avec permission denied.
2) Should I make the web root owned by www-data?
Généralement non. Si le serveur web peut écrire son propre contenu servi, une compromission conduit à des modifications persistantes. Préférez un utilisateur de déploiement propriétaire du contenu, avec le serveur web en lecture seule via un groupe ou des ACL. Rendre seulement des répertoires d’upload/cache écrivables, hors des chemins exécutables.
3) Is chmod 755 on directories always safe?
C’est courant et souvent acceptable pour du contenu statique public. Mais cela accorde traverse et lecture au monde. Dans des environnements corporate ou multi‑tenant, vous préférerez l’accès par groupe ou ACL, notamment pour le code source, les templates ou les configs qui ne doivent pas être lisibles par tous.
4) What’s better: groups or ACLs?
Les groupes sont plus simples et plus visibles. Les ACL sont plus précises et évitent de tout mettre dans un même groupe. Si vous avez une seule équipe et une seule pipeline de déploiement, les groupes suffisent. Si vous avez plusieurs équipes, des runners CI et une infra partagée, les ACL évitent les conflits.
5) Why do 403s show up only after deployments?
Les nouveaux fichiers héritent des permissions du processus créateur : umask, ACL par défaut et setgid parent comptent. Les anciens fichiers peuvent rester accessibles, donc le problème paraît intermittent. Corrigez la politique de création, pas seulement les fichiers actuels.
6) How do I check whether it’s AppArmor rather than Unix permissions?
Commencez par le log d’erreur : s’il indique permission denied alors que les bits mode semblent corrects, vérifiez AppArmor et ses logs. Sur Ubuntu, AppArmor est souvent en enforcement pour les serveurs web. Un refus sera enregistré par les composants d’audit système et référencé par le profil.
7) Why does “other execute but not read” on directories help?
o+x permet la traverse sans autoriser le listing du répertoire. Cela signifie que quelqu’un qui ne connaît pas les noms de fichiers ne peut pas facilement les énumérer. Ce n’est pas un substitut à un contrôle d’accès correct, mais cela réduit l’exfiltration accidentelle de la structure des répertoires.
8) Do I need to restart Apache/Nginx after changing filesystem permissions?
Non pour les bits mode et les changements de propriété ; le noyau les applique immédiatement. Vous devrez peut‑être redémarrer si vous avez changé l’appartenance à un groupe d’un utilisateur en cours d’exécution — les processus ne prennent pas automatiquement de nouveaux groupes supplémentaires.
9) Why does adding www-data to a group not work immediately?
Parce que les processus workers existants ont leur liste de groupes fixée au démarrage. Après modification de l’appartenance à un groupe, rechargez/redémarrez le service pour que les nouveaux workers héritent de la liste de groupes mise à jour.
10) What’s the safest layout for writable paths like uploads?
Mettez les répertoires écrivables en dehors de l’arbre code/static, donnez à l’application exactement les permissions d’écriture nécessaires, et assurez‑vous que le serveur web n’exécute pas de contenu depuis ces chemins. Si vous devez servir des uploads, servez‑les en statique, pas en exécutable.
Conclusion : prochaines étapes sans mauvaises surprises
Les 403 liés aux permissions ne sont pas mystérieux. Ils sont déterministes : quelque part sur le chemin, l’utilisateur du serveur web ne peut pas traverser ou lire. Corriger cela ne nécessite pas 777. Cela nécessite un modèle de propriété que vous pouvez expliquer à un collègue en manque de sommeil à 3h du matin.
Faites ceci ensuite :
- Ajoutez
namei -let «tester comme www-data» à votre runbook. Rendez‑le obligatoire. - Choisissez un schéma de permissions (groupe partagé avec setgid, ou ACLs) et déployez‑le pour chaque site. La cohérence est une fonctionnalité de sécurité.
- Séparez les chemins écrivables du web root. Votre futur vous enverra une note de remerciement lors du prochain incident.
- Auditez les répertoires world‑writable et supprimez‑les. Si quelque chose casse, ce n’est pas une raison pour abandonner — c’est la preuve que l’audit était nécessaire.
Si vous traitez les permissions comme de l’architecture plutôt que du nettoyage, les 403 deviennent une réparation rapide, pas un personnage récurrent de votre rotation d’astreinte.