PrestaShop : erreur 500 après une mise à jour ou un changement de version PHP

Une erreur 500 sur votre back-office apparaît presque toujours après un changement : une mise à jour de module, une montée de version de PrestaShop, ou un basculement de version PHP décidé par l’hébergeur. Le serveur renvoie une page blanche parce que PHP s’est arrêté avant d’écrire quoi que ce soit, et le navigateur ne dit rien de plus. C’est la panique. Voici la séquence que je suis, dans cet ordre, sur les boutiques 1.7, 8.x et 9.x.

Voir l’erreur réelle plutôt que la page blanche

Tant que le mode debug est éteint, PrestaShop intercepte l’exception et renvoie un écran vide, et la constante qui commande cet affichage vit dans config/defines.inc.php, en dur dans le fichier (l’écran du back-office ne fait que réécrire cette ligne). Sans accès à l’administration, éditez donc le fichier en SSH ou en FTP. Un écran qui reste vide après cette bascule relève alors d’un autre scénario, celui de la page blanche sans code d’erreur.

Un point souvent ignoré : passer _PS_MODE_DEV_ à true bascule aussi _PS_ENV_ sur dev, le cache et les journaux changent de dossier, et votre première page rechargée sera lente le temps de recompiler. C’est normal. Sur PrestaShop 9, un second commutateur permet de ne garder que les erreurs fatales, ce qui évite de noyer le message utile sous les dépréciations PHP.

<?php
// config/defines.inc.php : la valeur est en dur dans le fichier, le
// back-office ne fait que la reecrire. Sans acces au back-office, on edite
// le fichier en FTP ou en SSH.

if (!defined('_PS_MODE_DEV_')) {
    define('_PS_MODE_DEV_', true);
}

// PrestaShop 9 ajoute ce commutateur : il masque les avertissements et les
// notices pour ne laisser que les erreurs fatales, bien plus lisibles.
if (!defined('_PS_DISPLAY_ONLY_ERRORS_')) {
    define('_PS_DISPLAY_ONLY_ERRORS_', true);
}

// Le mode debug active aussi _PS_DEBUG_SQL_ et bascule _PS_ENV_ sur "dev",
// donc le cache et les journaux changent de dossier : var/cache/dev et
// var/logs/dev-AAAA-MM-JJ.log. C'est normal, et a remettre a false ensuite.

Je remets la valeur à false dès la panne comprise. En production, cet affichage expose vos chemins serveur et des fragments de requêtes. Pensez-y.

Lire les journaux Symfony au bon endroit

La confusion la plus fréquente porte sur le nom du fichier. PrestaShop écrit dans var/logs/, pas var/log/, et le gestionnaire Monolog déclaré dans app/config/config_prod.yml est de type rotating_file : votre fichier du jour s’appelle donc prod-2026-09-28.log, et trente fichiers sont conservés par défaut. Chercher un prod.log sans date mène à une impasse (j’y ai perdu du temps).

Ce journal ne capte que ce qui est passé par le noyau Symfony. Une erreur fatale survenue avant le démarrage du noyau, typiquement une classe manquante chargée par l’autoloader historique, n’y figure pas : elle atterrit dans le journal d’erreurs de votre serveur web. Regardez toujours les deux.

# Le handler Monolog "nested" est de type rotating_file : le nom du fichier
# porte la date du jour et 30 fichiers sont conserves par defaut.
cd /var/www/boutique
ls -lt var/logs/ | head -n 15

# Erreurs du jour en environnement de production
tail -n 100 "var/logs/prod-$(date +%Y-%m-%d).log"

# Toutes les lignes critiques conservees, du plus ancien au plus recent
grep -h -E 'CRITICAL|ERROR' var/logs/prod-*.log | tail -n 40

# Le serveur web tient son propre journal, souvent plus parlant sur un 500
tail -n 50 /var/log/apache2/error.log

La trace nomme le fichier fautif et la ligne exacte, et elle suffit presque toujours à désigner le module en cause, parce qu’un cœur PrestaShop non modifié ne casse pas tout seul. Notez ce nom, il servira à l’étape suivante. Quand la trace désigne au contraire un fichier du cœur non modifié, je m’arrête pour vérifier si le serveur a été compromis avant de corriger quoi que ce soit.

Vérifier que la version de PHP correspond à la branche

Les bornes de compatibilité sont codées en dur dans install-dev/install_version.php. PrestaShop 8.2.3 accepte PHP 7.2.5 à 8.1, PrestaShop 9.0.0 accepte PHP 8.1 à 8.4, et la documentation développeur donne les mêmes chiffres : un hébergeur qui passe votre boutique 8.2 sur PHP 8.2 la place hors de la plage supportée. En général, je vois le back-office tomber là, au tout premier chargement.

Le second suspect est la mémoire. La documentation fixe memory_limit à 256 Mo minimum (un plancher, pas une cible). Pour ma part, je monte à 512 Mo sur une boutique chargée en modules. Lancez le script ci-dessous, il affiche la version, les limites et les douze extensions exigées.

<?php
// verif-serveur.php, a deposer a la racine puis a supprimer apres lecture.
header('Content-Type: text/plain; charset=utf-8');

echo 'PHP ' . PHP_VERSION . ' (' . PHP_SAPI . ')' . PHP_EOL;
echo 'memory_limit        : ' . ini_get('memory_limit') . PHP_EOL;
echo 'max_execution_time  : ' . ini_get('max_execution_time') . PHP_EOL;
echo 'upload_max_filesize : ' . ini_get('upload_max_filesize') . PHP_EOL;
echo 'max_input_vars      : ' . ini_get('max_input_vars') . PHP_EOL;

// Extensions exigees par la documentation developpeur de PrestaShop 8 et 9.
$requises = [
    'curl', 'dom', 'fileinfo', 'gd', 'iconv', 'intl', 'json',
    'mbstring', 'openssl', 'pdo_mysql', 'simplexml', 'zip',
];

foreach ($requises as $extension) {
    echo str_pad($extension, 22) . (extension_loaded($extension) ? 'presente' : 'MANQUANTE') . PHP_EOL;
}

// Bornes codees en dur dans install-dev/install_version.php :
//   PrestaShop 8.2.3 : PHP 7.2.5 a 8.1
//   PrestaShop 9.0.0 : PHP 8.1 a 8.4
echo 'PHP_VERSION_ID      : ' . PHP_VERSION_ID . PHP_EOL;

Si une extension manque ou si la version sort de la plage supportée, la correction se fait chez votre hébergeur avant toute autre manipulation, et je ne cherche pas de module coupable tant que l’environnement n’est pas conforme. Les limites, elles, se règlent depuis la boutique.

cd /var/www/boutique

# Sur PHP-FPM ou CGI, un .htaccess ne peut pas fixer memory_limit.
cat > .user.ini <<'INI'
memory_limit = 512M
max_execution_time = 300
max_input_vars = 5000
INI

# Le fichier est relu au bout de user_ini.cache_ttl secondes (300 par defaut).
php -i | grep -E 'user_ini.filename|user_ini.cache_ttl|^memory_limit'

# Avec Apache et mod_php, passer plutot par le vhost :
#   php_value memory_limit 512M
#   php_value max_execution_time 300

Neutraliser un module fautif depuis la base

Voici le piège que je vois le plus souvent dans les tutoriels : passer ps_module.active à zéro ne désactive rien. La requête qui construit la liste des modules à exécuter, dans Hook::getAllHookRegistrations(), fait une jointure interne sur ps_module_shop et ne regarde jamais la colonne active. Un module dont la ligne ps_module_shop subsiste continue donc d’être appelé sur ses hooks, et votre erreur 500 persiste.

La méthode Module::disable() fait deux choses. Elle supprime les lignes de ps_module_shop, puis met active à zéro s’il ne reste aucune association, et votre intervention manuelle doit reproduire ces deux gestes (les deux, pas un seul).

-- Sauvegarder la base avant toute ecriture.
SELECT m.id_module, m.name, m.active, ms.id_shop
FROM ps_module m
LEFT JOIN ps_module_shop ms ON ms.id_module = m.id_module
WHERE m.name = 'monmodule';

-- Neutraliser le module. Hook::getHookModuleExecList fait une jointure
-- interne sur ps_module_shop et ne regarde jamais ps_module.active :
-- supprimer l'association boutique est ce qui coupe reellement les hooks.
DELETE ms FROM ps_module_shop ms
JOIN ps_module m ON m.id_module = ms.id_module
WHERE m.name = 'monmodule';

UPDATE ps_module SET active = 0 WHERE name = 'monmodule';

-- Controle : aucune ligne ps_module_shop ne doit subsister.
SELECT m.name, m.active, COUNT(ms.id_shop) AS boutiques
FROM ps_module m
LEFT JOIN ps_module_shop ms ON ms.id_module = m.id_module
WHERE m.name = 'monmodule'
GROUP BY m.id_module, m.name, m.active;

Attention aux surcharges : Module::disable() désinstalle aussi les fichiers que le module a déposés dans override/. Une requête SQL ne fait pas ce nettoyage, et une surcharge orpheline suffit à maintenir l’erreur, donc déplacez ces fichiers à la main, puis videz le cache. D’ailleurs, je retrouve bien d’autres surprises dans le dossier override/, et les cinq causes d’un override ignoré recoupent largement celles d’une surcharge qui provoque un 500.

cd /var/www/boutique

# PrestaShop 8 : un dossier de cache par environnement.
#   var/cache/prod, var/cache/dev, et var/cache/<env>/class_index.php
# PrestaShop 9 : un sous-dossier par application sous l'environnement.
#   var/cache/prod/front, var/cache/prod/admin, var/cache/prod/admin-api
rm -rf var/cache/dev var/cache/prod

# Smarty compile aussi dans var/cache/<env>/smarty, pas dans l'ancien /cache.
chown -R www-data:www-data var

Reprendre les droits de fichiers et le .htaccess

Après une restauration ou une migration, vos fichiers appartiennent souvent à un autre utilisateur système que celui qui fait tourner PHP. PrestaShop ne peut plus écrire dans var/, la compilation du conteneur Symfony échoue, et le serveur renvoie 500 : vérifiez d’abord quel utilisateur exécute PHP, puis appliquez 755 sur les répertoires et 644 sur les fichiers (jamais 777).

Le .htaccess arrive ensuite. PrestaShop le régénère entièrement à l’enregistrement des URL simplifiées, donc rien n’y est précieux sauf vos ajouts manuels (sauvegardez-les avant). Mettez-le hors service et testez le code de retour. Passer de 500 à 302 désigne le fichier sans ambiguïté.

cd /var/www/boutique

# 755 pour les repertoires, 644 pour les fichiers.
find . -type d -not -path './var/cache/*' -exec chmod 755 {} +
find . -type f -not -path './var/cache/*' -exec chmod 644 {} +
chmod 755 bin/console

# Le proprietaire doit etre l'utilisateur du serveur web.
ps -eo user,comm | grep -E 'apache2|php-fpm|nginx' | sort -u
chown -R www-data:www-data var img upload download translations
cd /var/www/boutique

cp -a .htaccess ".htaccess.sauvegarde-$(date +%Y%m%d-%H%M)"
mv .htaccess .htaccess.hors-service

# Un 500 qui devient un 200 ou un 302 designe le fichier comme coupable.
curl -o /dev/null -s -w '%{http_code}\n' https://exemple.fr/admin123/

# Regeneration propre : back-office, Parametres de la boutique,
# Trafic & SEO, puis enregistrer le formulaire des URL simplifiees.

Une fois l’accès rétabli, réactivez les URL simplifiées depuis votre back-office plutôt que de recopier un ancien fichier, le contenu généré dépend de la version installée. Je reprends ce type de diagnostic sur d’autres pannes dans les articles du blog.

Se donner un point de retour pour la prochaine fois

Une erreur 500 se règle en dix minutes quand une sauvegarde saine existe, et en une journée sinon. Programmez donc un export nocturne de la base et une archive des dossiers qui contiennent du code propre à votre boutique : modules, themes, override, img et app/config. Le reste est réinstallable depuis l’archive officielle (gardez votre numéro de version exact sous la main).

Deux détails font la différence. Le premier est le contrôle d’intégrité juste après l’écriture, parce qu’une archive tronquée par un disque plein ne se découvre jamais au bon moment. Le second est la destination. Une copie stockée sur le serveur qu’elle doit sauver ne protège de rien : poussez vos archives sur un espace distant après le script.

#!/usr/bin/env bash
set -euo pipefail

RACINE=/var/www/boutique
CIBLE=/var/backups/prestashop
HORO=$(date +%Y%m%d-%H%M)
mkdir -p "$CIBLE"

# Les identifiants restent dans ~/.my.cnf, jamais dans la ligne de commande.
mysqldump --defaults-file=/root/.my.cnf \
  --single-transaction --quick --routines \
  --default-character-set=utf8mb4 boutique \
  | gzip -9 > "$CIBLE/base-$HORO.sql.gz"

tar -czf "$CIBLE/fichiers-$HORO.tar.gz" \
  -C "$RACINE" modules themes override img app/config

# Verification immediate : une archive illisible ne sert a rien.
gzip -t "$CIBLE/base-$HORO.sql.gz"
tar -tzf "$CIBLE/fichiers-$HORO.tar.gz" > /dev/null

find "$CIBLE" -type f -mtime +14 -delete

Ce script ne remplace pas une vraie politique de sauvegarde chez votre hébergeur. Il ne gère ni la rotation ni les versions successives, et sur une boutique de plusieurs gigaoctets il finira par buter sur le temps d’exécution alloué. Si le diagnostic dépasse le temps que vous pouvez y consacrer, je prends la main. Écrivez-moi avec la trace du journal.

Questions fréquentes

Comment activer le mode debug sans accès au back-office ?

Le fichier config/defines.inc.php contient la ligne define('_PS_MODE_DEV_', true); dans un bloc if (!defined(...)). Remplacez false par true en SSH ou en FTP, puis rechargez la page. Sur PrestaShop 9, passer _PS_DISPLAY_ONLY_ERRORS_ à true masque les avertissements et ne laisse que l’erreur fatale.

Cette bascule change aussi votre environnement applicatif : le cache et les journaux passent de prod à dev, cherchez donc les traces dans var/logs/dev-AAAA-MM-JJ.log tant que le mode debug reste actif, et remettez la valeur à false une fois le correctif appliqué.

Où se trouvent les journaux d’erreurs sur PrestaShop 8 ou 9 ?

Dans var/logs/, au pluriel (c’est l’erreur classique). Le gestionnaire Monolog configuré dans app/config/config_prod.yml est un rotating_file : le nom du fichier porte la date, comme prod-2026-09-28.log, et trente fichiers sont conservés par défaut. Un fichier prod.log sans date n’existe pas dans ces versions.

Ce journal ne couvre que le périmètre du noyau Symfony, et les erreurs fatales survenues avant son démarrage, ou les erreurs de configuration Apache, restent uniquement dans le journal de votre serveur web. Consultez les deux sources avant de conclure.

Désactiver un module en base suffit-il à couper ses hooks ?

Non, pas si vous vous contentez de UPDATE ps_module SET active = 0, car la requête qui construit la liste d’exécution des hooks joint ps_module_shop et ignore la colonne active. Tant qu’une ligne existe dans ps_module_shop pour votre boutique courante, le module est appelé.

La désactivation réelle consiste à supprimer les lignes de ps_module_shop puis à mettre active à zéro, ce que fait Module::disable(), et cette méthode désinstalle en plus les surcharges du module, ce qu’une requête SQL ne fait pas. Déplacez donc manuellement les fichiers correspondants dans override/.

Une mise à jour PHP peut-elle provoquer une erreur 500 ?

Oui, et c’est celui que je rencontre le plus souvent. Les bornes sont écrites dans install-dev/install_version.php : PrestaShop 8.2.3 couvre PHP 7.2.5 à 8.1, PrestaShop 9.0.0 couvre PHP 8.1 à 8.4, et une boutique 8.2 basculée sur PHP 8.2 ou 8.3 sort de la plage supportée.

Même dans la plage officielle, un module ancien peut utiliser une fonction supprimée. Le journal Symfony nomme alors la classe fautive. Vérifiez aussi memory_limit, fixé à 256 Mo minimum par la documentation, et les douze extensions requises, dont intl, gd et fileinfo.

Comment vider le cache manuellement ?

Sur PrestaShop 8, supprimez var/cache/dev et var/cache/prod. Le conteneur Symfony, l’index des classes et les templates Smarty compilés y sont tous regroupés, et PrestaShop 9 ajoute un niveau : chaque application dispose de son sous-dossier, soit var/cache/prod/front, var/cache/prod/admin et var/cache/prod/admin-api.

Rien n’est perdu, tout est régénéré au chargement suivant. En revanche, rendez la main au serveur web après coup : un var/ appartenant à root bloquera la recompilation et remplacera votre erreur d’origine par une nouvelle. Si vous préférez un accompagnement durable sur ces sujets, je propose du développement sur mesure.

Commentaires

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Un projet ? Parlons-en.

Devis gratuit et sans engagement. Je réponds dans les douze heures.