En bref : sur un site en production, display_errors = Off a sa place dans le php.ini, avec display_startup_errors = Off, log_errors = On et un journal d'erreurs situé en dehors de la racine web. En outre, les CMS et frameworks doivent tourner en mode production, sinon leurs propres pages d'erreur montrent les détails. Verrouillées avec php_admin_flag et vérifiées après chaque mise à jour, les erreurs restent dans le journal au lieu d'apparaître dans le navigateur.
Ce que révèle un seul message d'erreur
Un avertissement en haut de la page, une « Fatal error » à la place du récapitulatif de commande : de telles sorties semblent inoffensives. Pour un attaquant, ce sont une carte. Elles montrent des chemins de fichiers complets, des noms de bases de données et de tables, les bibliothèques utilisées et souvent la version de PHP.
Dans des situations comme PHP2Shell, où l'on recherche massivement des serveurs vulnérables, un serveur devrait divulguer aussi peu que possible sur lui-même. Cela ne ferme aucune faille dans l'interpréteur PHP, mais complique chaque étape suivante après une intrusion et rend les attaques ciblées contre votre application plus laborieuses.
S'y ajoute la protection des données. Les messages d'erreur peuvent contenir des parties de requêtes, par exemple des adresses e-mail issues de formulaires. Ce qui apparaît dans le navigateur d'un visiteur n'est plus sous votre contrôle.
Les valeurs par défaut ne sont pas conçues pour la production
Beaucoup supposent que PHP masque les erreurs de lui-même. Le manuel PHP dit autre chose : la valeur par défaut intégrée de display_errors est 1, et display_startup_errors est lui aussi activé par défaut depuis PHP 8.0. Ce n'est que le modèle fourni php.ini-production qui met les deux sur Off. Le manuel qualifie expressément display_errors de fonction de développement qui ne devrait jamais être utilisée sur des systèmes connectés à Internet.
Savoir si votre serveur utilise le modèle de production n'a rien d'évident. Les images de conteneur démarrent parfois sans aucun php.ini, les panneaux d'hébergement définissent leurs propres valeurs, et après un déménagement, on copie volontiers le modèle de développement.
display_errors off : les paramètres en un coup d'œil
Les valeurs suivantes sont judicieuses pour des serveurs de production. Les constantes comme E_ALL ne fonctionnent que dans le php.ini ; dans la configuration d'Apache, elles n'ont selon le manuel PHP aucune signification et doivent être indiquées sous forme de nombre.
| Paramètre | Production | Objectif |
|---|---|---|
display_errors |
Off |
aucune erreur dans le navigateur |
display_startup_errors |
Off |
aucune erreur au démarrage de PHP dans la sortie |
log_errors |
On |
écrire les erreurs dans le journal |
error_log |
chemin en dehors de la racine web | fichier journal propre par site ou pool |
error_reporting |
E_ALL |
journaliser toutes les erreurs, ne pas les afficher |
html_errors |
Off |
texte brut au lieu de HTML dans les journaux |
zend.exception_ignore_args |
On |
pas d'arguments de fonction dans les traces d'appels |
fatal_error_backtraces |
0 jusqu'à la mise à jour (PHP 8.5 uniquement) |
mesure transitoire selon php.net |
zend.exception_ignore_args est particulièrement précieux. Sans ce paramètre, les traces d'appels dans les journaux et pages d'erreur peuvent contenir les arguments des appels de fonction, donc aussi des mots de passe ou des jetons en cours de traitement.
; php.ini de la SAPI web
display_errors = Off
display_startup_errors = Off
log_errors = On
error_log = /var/log/php/website-fehler.log
error_reporting = E_ALL
html_errors = Off
zend.exception_ignore_args = On
; PHP 8.5 uniquement, mesure transitoire jusqu'à la mise à jour vers 8.5.12 :
; fatal_error_backtraces = 0
Dans le pool FPM, verrouillez en plus les valeurs les plus importantes, afin qu'aucune application ne les réactive avec ini_set() :
; par ex. /etc/php/8.3/fpm/pool.d/seite1.conf
php_admin_flag[display_errors] = off
php_admin_flag[display_startup_errors] = off
php_admin_flag[log_errors] = on
php_admin_value[error_log] = /var/log/php/seite1-fehler.log
Redémarrez ensuite le service PHP concerné. Le fonctionnement exact du verrouillage est expliqué dans l'article php_admin_value : verrouiller les paramètres contre l'écrasement. Vous trouverez une configuration de base complète dans Configurer php.ini de façon sûre.
Le second interrupteur : le mode débogage de l'application
PHP n'est que la moitié du travail. Les frameworks modernes interceptent eux-mêmes les erreurs et affichent leurs propres pages d'erreur, souvent très détaillées. En mode débogage, elles contiennent des extraits de code source, des valeurs de configuration et parfois des variables d'environnement. Ces pages apparaissent même lorsque display_errors est désactivé dans PHP.
Vérifiez donc, pour chaque application, son propre interrupteur. Pour WordPress, ce sont WP_DEBUG et WP_DEBUG_DISPLAY dans wp-config.php, pour Laravel et Symfony APP_DEBUG et l'environnement dans le fichier .env, pour les autres systèmes le mode production selon la documentation de l'éditeur. Sur un système en production, le mode débogage doit rester désactivé en permanence ; même « juste un instant pour tester » est un risque.
La manière de traiter proprement les erreurs dans l'application, au lieu de les supprimer, est décrite dans Erreurs et exceptions en PHP.
Trouver les sorties d'erreur avant que d'autres ne le fassent
Un réglage unique ne suffit pas. Les sorties d'erreur reviennent après des mises à jour, des déménagements ou des modifications du panneau. Une courte procédure de vérification aide :
- Vérifier les valeurs effectives dans le contexte web, pas seulement en ligne de commande, par exemple avec un fichier de test déposé brièvement puis supprimé aussitôt.
- Contrôler le mode débogage de chaque application dans son fichier de configuration.
- Parcourir les pages livrées à la recherche de textes d'erreur typiques, comme dans le bloc de code ci-dessous.
- Dans le moteur de recherche, avec une requête
site:sur votre domaine, chercher des termes comme « Fatal error » ou « Warning ». - Vérifier que le journal d'erreurs est bien alimenté et ne se trouve pas dans la racine web.
# Parcourir les pages livrées à la recherche de textes d'erreur PHP (uniquement vos propres domaines)
for url in https://www.example.de/ https://www.example.de/kontakt/; do
curl -s "$url" | grep -E -o "(Fatal error|Warning|Notice|Deprecated|Stack trace)[^<]{0,80}" | head -3
done
# Le journal d'erreurs est-il alimenté ?
ls -l /var/log/php/
Cacher les erreurs ne remplace pas leur correction. Un journal plein d'avertissements indique un besoin de maintenance, et après une intrusion, c'est souvent une trace importante. Ce que vous y journalisez et ce que vous ne journalisez pas est décrit dans Journaliser en sécurité : consigner les erreurs sans fuite de données.
Conclusion : silencieux dans le navigateur, bavard dans le journal
La règle pour les serveurs de production est simple : les erreurs vont dans le journal, pas sur le site. En pratique, elle échoue à cause de valeurs par défaut non conçues pour la production, d'interrupteurs de débogage dans les applications et de configurations qui régressent après des mises à jour ou des déménagements.
Qui désactive display_errors et display_startup_errors, verrouille les valeurs dans le pool, contrôle le mode débogage de l'application et vérifie régulièrement le résultat ferme l'une des sources les plus fréquentes de divulgation inutile d'informations. L'effort est faible, l'effet dure tant que la vérification reste partie intégrante de chaque mise à jour.
Un point ne doit pas être oublié : le journal d'erreurs n'est pas une poubelle. Qui le lit régulièrement repère les mises à jour défectueuses, les fonctions obsolètes et parfois aussi les premières traces d'une attaque, bien avant que les clients ne remarquent quoi que ce soit.
Comment PHP SOS aide-t-il concrètement ?
- Vérifier : nous lisons les paramètres d'erreur effectifs par site et par pool et parcourons les pages livrées à la recherche de sorties d'erreur.
- Basculer : nous définissons les paramètres de PHP et de l'application pour la production et les verrouillons contre l'écrasement.
- Mettre en place les journaux : les journaux d'erreurs se trouvent en dehors de la racine web, sont soumis à rotation et accessibles à vos développeurs.
- Maintenir durablement : dans le contrat de maintenance de sécurité, nous revérifions les paramètres après chaque mise à jour.
Questions fréquentes
display_errors n'est-il pas de toute façon désactivé dans PHP ?
Pas automatiquement. Selon le manuel PHP, la valeur par défaut intégrée de display_errors est 1. Ce n'est que le modèle fourni php.ini-production qui la met sur Off. Savoir si votre serveur utilise ce modèle dépend de la distribution, du panneau ou de l'image de conteneur, et cela devrait être vérifié.
display_errors = Off suffit-il si WordPress ou Laravel tournent en mode débogage ?
Non. De nombreux CMS et frameworks apportent leurs propres pages d'erreur, qui peuvent afficher des détails indépendamment de PHP. Désactivez donc aussi le mode débogage de l'application, par exemple WP_DEBUG_DISPLAY pour WordPress ou APP_DEBUG pour Laravel et Symfony.
Où vont les erreurs lorsqu'elles ne sont plus affichées ?
Avec log_errors = On, PHP les écrit dans le fichier désigné par error_log, ou dans le journal du mode d'exploitation concerné, par exemple le journal d'erreurs d'Apache ou de PHP-FPM. Le fichier devrait se trouver en dehors de la racine web et être soumis à une rotation régulière.
Quel rapport entre fatal_error_backtraces et PHP2Shell ?
Le paramètre existe à partir de PHP 8.5 et est actif par défaut. php.net cite fatal_error_backtraces=0 suivi d'un redémarrage comme l'une des mesures transitoires pour PHP 8.5, tant que la mise à jour vers 8.5.12 est en attente. Il ne remplace pas la mise à jour.