PHP OPcache : paramètres recommandés pour optimiser les performances

Réglages OPcache recommandés pour PrestaShop 8/9 : dimensionner mémoire et tables, ajuster interned strings, choisir stratégie d’invalidation et valider après warmup.

Écran d'ordinateur avec du code OPcache et éléments graphiques numériques.

Le réglage par défaut de PHP OPcache est rarement dimensionné pour une boutique PrestaShop réelle (core + vendor Symfony + modules). Résultat classique : OPcache “marche”, mais il évince des scripts, se fragmente, redémarre, ou invalide trop agressivement. Dans ces conditions, vous payez quand même le coût de compilation Zend à chaque pic de trafic, et le TTFB ne bouge pas (ou bouge de façon instable).

« OPcache improves PHP performance by storing precompiled script bytecode in shared memory, thereby removing the need for PHP to load and parse scripts on each request. » — Documentation officielle PHP, Zend OPcache (php.net)

Ce papier est écrit pour PHP 8.2/8.3 en production (FPM), avec un focus e‑commerce/PrestaShop 8.x/9.x. Pour les compatibilités de versions PHP côté PrestaShop 9 (et les incohérences de doc), voir : PrestaShop 9 : versions PHP recommandées et incohérences de documentation.

Ce que vous devez garder en tête avant de “monter les curseurs” :

  • OPcache se dimensionne (RAM + tables) sur la base d’un état mesuré, pas sur une valeur “standard”.
  • En e‑commerce, les problèmes viennent souvent d’un mix (OPcache + FPM + MySQL + cache applicatif). OPcache bien réglé ne remplace rien : il évite juste de gaspiller du CPU à recompiler.
  • Les réglages d’invalidation sont un sujet opérationnel (déploiements) autant qu’un sujet performance.

Table des matières :

  1. OPcache en production : ce que vous gagnez réellement (et ce que ça ne règle pas)
  2. Méthode de dimensionnement : mesurer avant de “mettre 512M”
  3. Paramètres qui changent la donne : mémoire, tailles de tables et chaînes internées
  4. Invalidation et déploiements : validate_timestamps, revalidate_freq et le risque “code fantôme”
  5. Paramètres avancés : preloading, huge pages, file_cache et (non) intérêt du JIT
  6. Configurations recommandées (PHP-FPM) + checklist de vérification opérationnelle

OPcache est un cache d’opcodes (bytecode Zend) stocké en mémoire partagée. Il court-circuite le cycle “lire le fichier PHP → lexer/parser → compiler” à chaque requête. Sur une boutique PrestaShop, ça touche immédiatement : le kernel Symfony (PrestaShop 9), l’autoload Composer, la surcharge de classes, les templates Twig compilés, et une grande partie des modules.

Concrètement, OPcache évite une partie du “coût fixe” de chaque requête PHP. C’est particulièrement visible :

  • sur des pages qui chargent beaucoup de classes (front office + modules),
  • sur le back-office (beaucoup de contrôleurs, services, classes utilitaires),
  • sur les endpoints API / webhooks (souvent courts, mais sensibles à la latence “startup”).

Mini-scénario réaliste (agence / boutique française) : vous hébergez un PrestaShop 8/9 sur un VPS (Paris/Gravelines/Strasbourg, peu importe le provider), avec 60 modules. Vous lancez une campagne Ads à 10h, le trafic double, et d’un coup vos workers FPM passent plus de temps à compiler qu’à exécuter. OPcache “activé” mais trop petit se met à évincer des scripts : vous voyez monter le CPU user, et le TTFB devient erratique (p95 qui explose), surtout quand le set de scripts parcouru s’élargit (navigation, recherche, facettes, pages CMS, tunnel).

Le gain est principalement CPU (compilation), mais aussi I/O (moins de lectures disque) si vos endpoints touchent beaucoup de scripts différents. Par contre, OPcache ne corrige ni un MySQL lent, ni un front qui fait 200 requêtes AJAX, ni un back-office qui déclenche 120 requêtes SQL par page. Si votre goulot est la base, commencez par instrumenter et couper les requêtes lentes : Requêtes MySQL lentes PrestaShop : activer slow query log et Développeur MySQL : optimiser requêtes, schémas et performances en production.

Enfin, OPcache n’est pas un cache HTTP. Il n’économise pas le rendu HTML ni les appels SQL : il réduit le coût de “démarrer PHP et charger le code”. Dans une architecture e‑commerce sérieuse, il se combine à des caches applicatifs (Redis), un reverse proxy (Varnish/HAProxy) et un CDN. Si vous avez des pics (soldes, campagnes), lisez plutôt l’approche multi-niveaux : PrestaShop soldes : optimiser caches, CDN et Varnish pour trafic élevé et, pour Redis, Redis PrestaShop : configurer le cache sur VPS ou serveur dédié.

Point important : OPcache peut “déplacer” le problème. Une fois la compilation éliminée, vous révélez souvent le vrai goulot (requêtes SQL, appels externes, contention FPM). C’est une bonne nouvelle : vous sortez du bruit et vous diagnostiquez sur des symptômes plus stables.

OPcache en production : ce que vous gagnez réellement (et ce que ça ne règle pas)

Tuner OPcache sans métriques mène aux paramètres “au pif” (et aux incidents). Sur PHP-FPM, la référence est opcache_get_status() et surtout l’état opcache_statistics + memory_usage. Vous pouvez exposer ces infos via un endpoint admin protégé, ou passer par un script CLI qui dump en JSON, à condition de garder la sécurité en tête (ne jamais exposer ça publiquement).

Quelques précisions de méthode (souvent oubliées) :

  • Mesure côté FPM, pas côté CLI : php -r 'var_dump(opcache_get_status());' reflète l’OPcache du SAPI CLI, qui est distinct. Pour mesurer l’OPcache réellement utilisé par votre site, faites exécuter le script via Nginx/Apache → PHP-FPM (même pool).
  • Si vous avez plusieurs pools (ex. pool back-office séparé, ou plusieurs vhosts), chaque pool a son propre segment OPcache : votre memory_consumption=256 est par pool, pas “pour le serveur entier”.

Concrètement, surveillez :

  • hit rate (opcache_statistics['opcache_hit_rate']) : en prod, viser > 99% est réaliste dès que le trafic est stable.
  • scripts (num_cached_scripts vs max_cached_keys) : si vous êtes proche de la limite, vous cachez “à trous” et perdez l’intérêt.
  • memory_usage (used_memory, free_memory, wasted_memory) : une mémoire “wasted” qui grimpe finit par déclencher des redémarrages selon la politique.
  • restarts (oom_restarts, hash_restarts, manual_restarts) : si ça bouge, c’est rarement acceptable (sauf déploiements).

Un tableau de lecture rapide (utile en runbook) :

Symptôme observé Ce que ça signifie souvent Réglage à revoir
num_cached_scripts proche de max_cached_keys table de hachage trop petite, cache incomplet opcache.max_accelerated_files
oom_restarts > 0 mémoire OPcache insuffisante (ou trop fragmentée) opcache.memory_consumption, parfois max_wasted_percentage
hash_restarts > 0 table de hachage saturée opcache.max_accelerated_files
wasted_memory augmente vite fragmentation (déploiements fréquents, invalidations) stratégie d’invalidation + max_wasted_percentage
hit rate < 99% en régime évictions / redémarrages / trop de scripts non cachés dimensionnement global + restarts

Pour PrestaShop, une façon simple d’estimer le besoin est de compter le volume de PHP effectivement chargé : le core, vendor/, et vos modules actifs. Un find sur l’arborescence donne déjà un ordre de grandeur ; ensuite, c’est le profil de trafic (front vs back-office vs API) qui fixe le set de scripts réellement utilisés. Ne confondez pas “nombre de fichiers PHP” et “scripts mis en cache” : OPcache ne retient que ce qui a été exécuté.

Pratique recommandée : faites un warmup contrôlé après un redémarrage FPM (ou après un déploiement) pour stabiliser la mesure. Exemple simple (à adapter à votre shop et à vos protections) :

# Exemple minimal : chauffer quelques routes publiques
# (ne remplace pas un parcours applicatif complet)
for url in \
  "https://www.example.com/" \
  "https://www.example.com/3-categories" \
  "https://www.example.com/recherche?search_query=robe" \
  "https://www.example.com/promo"
do
  curl -sS -o /dev/null "$url"
done

Ensuite seulement, relevez les métriques OPcache. Sans warmup, vous risquez de conclure “ça passe” alors que le cache n’a pas encore rencontré le set réel (ex. tunnel, pages compte, pages modules, endpoints spécifiques).

Enfin, corrélez avec votre moteur PHP : FPM (Nginx/Apache) vs LSAPI (LiteSpeed) n’a pas le même comportement sous saturation. Si vous êtes sur LiteSpeed, vous avez des sig­naux spécifiques (WaitQ, contention) qui peuvent masquer un OPcache sous-dimensionné : Performances PHP LiteSpeed : cache opcode, LSAPI et suivi WaitQ.

Méthode de dimensionnement : mesurer avant de “mettre 512M”

Le trio qui fait (ou défait) un Zend OPcache en e‑commerce est : opcache.memory_consumption, opcache.max_accelerated_files, opcache.interned_strings_buffer. Le reste est secondaire tant que ces trois-là sont faux.

opcache.memory_consumption (en Mo) est la taille de la mémoire partagée pour stocker les scripts compilés. Sous-dimensionné, vous aurez des évictions et/ou des redémarrages par OOM ; surdimensionné, vous gaspillez de la RAM, ce qui peut pousser Linux à swapper (pire que tout). En pratique, sur une boutique PrestaShop 8/9 avec un catalogue et un parc de modules “standard agence” (40–80 modules), un point de départ réaliste est 256M ; sur des environnements très chargés (beaucoup de modules + back-office intensif + jobs), 384M à 512M n’est pas choquant — mais uniquement si vous mesurez used_memory et la croissance après warmup.

Astuce terrain : ne regardez pas seulement used_memory, regardez aussi la marge. Si après warmup vous n’avez plus que 5–10% de free_memory, le moindre ajout de module, une fonctionnalité activée, ou un parcours utilisateur qui exécute des scripts jusque-là “froids” peut déclencher des évictions (puis des compilations au pire moment : en charge).

opcache.max_accelerated_files fixe la taille de la table de hachage des scripts. Le piège : beaucoup de configs restent à 10k, alors qu’un vendor Symfony + modules peut dépasser. Si cette limite est atteinte, OPcache ne peut plus stocker de nouveaux scripts, et vous retombez dans la compilation à la volée. Les valeurs courantes sont 20000, 50000 ou 100000 selon la base de code. Sur PrestaShop 9 (Symfony plus présent, Twig, vendor/ plus dense), 50000 est souvent un minimum confortable.

Bon réflexe : gardez un ratio “confort” et pas “au chausse-pied”. Viser num_cached_scripts à ~60–80% de max_cached_keys en régime est une marge raisonnable pour absorber :

  • une mise à jour mineure (nouvelles classes),
  • l’activation d’un module,
  • un flux de trafic qui explore plus de pages (SEO, campagne produit, retargeting).

opcache.interned_strings_buffer (en Mo) stocke les chaînes internées (identifiants, noms de classes, clés…). Les frameworks et les autoloaders en consomment beaucoup. Sous-dimensionné, vous perdez un des gains mémoire et vous augmentez la pression GC. Sur des stacks Symfony/PrestaShop modernes : commencez à 16M, montez à 32M si interned_strings_usage['free_memory'] devient faible après warmup.

Deux paramètres “secondaires” mais utiles à comprendre en prod e‑commerce :

  • opcache.max_wasted_percentage : seuil à partir duquel OPcache peut déclencher un redémarrage interne pour récupérer la mémoire “wasted” (fragmentation). Plus le seuil est bas, plus vous “nettoyez” tôt, mais plus vous risquez des redémarrages si vous invalidez souvent.
  • opcache.save_comments=1 : gardez-le à 1 en prod. Beaucoup d’outils et de bibliothèques s’appuient sur les commentaires/docblocks (annotations, métadonnées, etc.). Le gain à désactiver est rarement un bon trade-off en boutique.

La définition et les options officielles sont documentées ici : OPcache Configuration.

Paramètres qui changent la donne : mémoire, tailles de tables et chaînes internées

Le plus gros piège opérationnel d’OPcache n’est pas la performance : c’est la cohérence code déployé vs code exécuté. Avec opcache.validate_timestamps=1, OPcache va vérifier (selon revalidate_freq) si les fichiers ont changé. C’est “safe” mais ça réintroduit des stat() sur le FS et une forme de variabilité, surtout sur stockage réseau, volumes conteneurs mal montés, ou NAS/NFS (cas rencontré sur certaines infra mutualisées / legacy).

En production maîtrisée (CI/CD, artefacts immuables, blue/green), la stratégie la plus propre est souvent : opcache.validate_timestamps=0 (aucune revalidation) + une action explicite au déploiement (reload PHP-FPM ou purge OPcache). C’est plus rapide et surtout déterministe, mais ça impose une discipline : si vous déployez des fichiers “en place” sans reload, vous pouvez servir du vieux bytecode. Si vous pratiquez les bascules atomiques (symlink vers un nouveau release) ou du blue/green, vous êtes déjà dans le bon modèle ; voir : Migration PrestaShop : audit technique et plan incrémental blue/green.

Dans ce modèle, le “file update” est géré par le cycle de vie du process PHP-FPM. En pratique :

  • déploiement (nouveau code),
  • reload FPM (gracieux) pour que les nouveaux workers chargent le nouvel OPcache,
  • warmup applicatif (optionnel mais conseillé),
  • monitoring sur 10–15 minutes (restarts, hit rate, latences).

opcache.revalidate_freq n’a de sens que si validate_timestamps=1. Sur un e‑commerce, le réglage “développement” (0 ou 1) est une anti‑optimisation en prod, parce que vous forcez trop de checks. Un compromis acceptable en prod “non immuable” est 60s ou 120s, mais assumez qu’un hotfix peut mettre jusqu’à 2 minutes à être pris en compte.

opcache.file_update_protection (par défaut 2s) protège contre le caching de fichiers en cours d’écriture : utile si vous déployez en rsync sur place (ce qui reste une mauvaise pratique), mais insuffisant contre des déploiements partiels, des permissions instables, ou des synchronisations interrompues. Si vous ne pouvez pas faire autrement que du “in-place”, la priorité n’est pas de jouer sur file_update_protection : c’est de sécuriser le déploiement (répertoire de release + bascule atomique + reload FPM).

Dernier point : évitez d’utiliser opcache_reset() “en panique” depuis une URL accessible. Si vous devez le faire (incident), restreignez très fortement l’accès (IP, auth, réseau interne), et préférez un reload FPM piloté par votre outil d’admin (systemd/Ansible), plus auditables.

Invalidation et déploiements : validate_timestamps, revalidate_freq et le risque “code fantôme”

Le preloading (opcache.preload) permet de charger et compiler au démarrage FPM un ensemble de fichiers, puis de les partager entre workers. Sur le papier, c’est tentant pour Symfony et PrestaShop 9. Dans les faits, ça devient rentable si (1) vous avez un cycle de déploiement propre (redémarrage FPM contrôlé), (2) vous maîtrisez la liste de fichiers, (3) vous avez vérifié qu’il n’y a pas d’effets de bord (initialisations globales, accès DB, lecture de config en dur).

Sur un codebase PrestaShop + modules, l’hétérogénéité rend le preloading fragile : un module qui exécute du code au require (ou au chargement d’un fichier “bootstrap”) peut casser le démarrage du pool FPM. Une approche pragmatique si vous voulez tester sans vous piéger :

  • ne préchargez que le core et une sélection minimaliste du vendor/ (components stables),
  • excluez les modules (ou chargez uniquement ceux que vous contrôlez),
  • testez d’abord sur un pool dédié (ou un environnement de staging identique),
  • validez que le démarrage FPM reste fiable (et que le rollback est trivial).

Les “huge pages” (opcache.huge_code_pages=1) peuvent améliorer les perfs en réduisant la pression TLB sur certains workloads, mais c’est très dépendant du kernel et de la config mémoire. Activez uniquement si vous savez valider (profiling CPU, bench reproductible) et si le kernel supporte correctement THP/huge pages. Sur de la prod e‑commerce, c’est un “nice to have”, pas un levier principal.

Le opcache.file_cache (cache disque persistant) est utile dans des environnements où la mémoire partagée est volatile ou limitée (certains mutualisés) et où le redémarrage FPM est fréquent. Sur un serveur dédié/VPS correctement dimensionné, vous en avez rarement besoin et vous ajoutez une variable (cohérence, permissions, I/O). Si vous l’utilisez malgré tout, vérifiez :

  • que le répertoire de cache est sur un stockage local fiable,
  • que les permissions sont strictes (éviter un dossier world-writable),
  • que le cache est purgé/renouvelé au rythme de vos déploiements.

Quant au JIT (opcache.jit*), il peut accélérer des calculs CPU lourds, mais la majorité des endpoints PrestaShop sont I/O bound (SQL, réseau, templates). Activer un JIT “par principe” est rarement mesurable et complique le diagnostic ; gardez-le pour des workloads identifiés (ex. traitement d’images en PHP pur, algos spécifiques, tâches batch CPU-heavy). Pour rester simple et prédictible sur un e‑commerce, une règle saine est : JIT désactivé tant que vous n’avez pas un benchmark qui prouve l’inverse.

Paramètres avancés : preloading, huge pages, file_cache et (non) intérêt du JIT

Voici deux bases de configuration (à adapter). Elles supposent PHP-FPM sur Linux, et une prod où vous acceptez un reload FPM à chaque déploiement. Si vous êtes sur cPanel et que vous devez déjà valider l’activation de l’extension, commencez par : OPcache PHP : activer et vérifier l’extension dans cPanel.

Avant de copier/coller : adaptez aussi en fonction de votre organisation. Exemple : si vous avez des cronjobs/commandes Symfony (bin/console) très fréquents et coûteux en CLI, opcache.enable_cli=1 peut devenir pertinent. Sinon, laissez-le à 0 pour éviter de consommer de la RAM sur le SAPI CLI sans bénéfice.

Profil “VPS correct” (2–4 vCPU, 8–16 Go RAM), PrestaShop 8/9 :

; php.ini / conf.d/opcache.ini
opcache.enable=1
opcache.enable_cli=0

; dimensionnement
opcache.memory_consumption=256
opcache.interned_strings_buffer=16
opcache.max_accelerated_files=50000
opcache.max_wasted_percentage=10

; stratégie déploiement : artefacts immuables + reload FPM
opcache.validate_timestamps=0
opcache.revalidate_freq=0

; compatibilité
opcache.save_comments=1

; optionnel sécurité : limiter l’usage des fonctions opcache_* à une arborescence
; (utile si vous avez plusieurs vhosts / mutualisation)
; opcache.restrict_api=/var/www

Profil “grosse base de code” (beaucoup de modules, back-office intensif) :

opcache.enable=1
opcache.enable_cli=0

opcache.memory_consumption=384
opcache.interned_strings_buffer=32
opcache.max_accelerated_files=100000
opcache.max_wasted_percentage=5

opcache.validate_timestamps=0
opcache.revalidate_freq=0

opcache.save_comments=1
; opcache.restrict_api=/var/www

Checklist de validation, à exécuter après warmup (trafic réel ou script qui parcourt front/back-office) :

1) Vérifiez le statut et les ratios : opcache_get_status(false) (voir la doc : https://www.php.net/manual/en/function.opcache-get-status.php). Un hit rate qui plafonne à 95–98% en charge est un signal d’un cache trop petit, d’un set de scripts trop large, ou de redémarrages.

2) Vérifiez l’absence de redémarrages pendant une fenêtre stable : oom_restarts et hash_restarts doivent rester à 0 en dehors des déploiements. Sinon, votre tuning est factuellement mauvais (et vous risquez des “micro-régressions” aléatoires au pire moment).

3) Vérifiez que vous n’êtes pas en limite “silencieuse” :

  • num_cached_scripts / max_cached_keys trop proche de 1 → augmentez opcache.max_accelerated_files.
  • free_memory très faible après warmup → augmentez opcache.memory_consumption (ou réduisez la surface de code exécutée, ex. modules inutiles).
  • interned_strings_usage['free_memory'] très faible → augmentez opcache.interned_strings_buffer.

4) Corrélez avec vos autres couches : FPM (process manager, pm.max_children), cache applicatif (Redis), et base. OPcache “bien réglé” peut faire ressortir un MySQL lent (parce que vous avez retiré du coût CPU côté PHP). Pour une approche globale (bench, PHP-FPM, OPcache, MySQL), voyez : Performance PrestaShop : benchmarks et optimisation PHP-FPM, OPCache, MySQL.

5) Vérifiez l’aspect opérationnel “déploiement” :

  • si validate_timestamps=0, assurez-vous que votre pipeline fait bien un reload FPM (et que c’est observable : logs, monitoring),
  • si vous faites du blue/green, vérifiez que le basculement est réellement atomique (pas de mélange de code entre releases).

Si vous cherchez une méthode reproductible pour décider “OPcache a aidé / n’a pas aidé” sans vous raconter d’histoires, basez-vous sur un protocole de mesure (TTFB, CPU system/user, req/s, latence p95) avant/après. La démarche outillée est détaillée ici : Audit performance PrestaShop : méthode en 6 étapes reproductibles.


À lire aussi