En bref : OPcache est le cache de bytecode intégré de PHP. Selon le manuel, il stocke du bytecode précompilé dans la mémoire partagée, de sorte que PHP n'a pas à recharger et à retraduire les scripts à chaque requête. Il est livré avec PHP depuis PHP 5.5.0. OPcache rend PHP plus rapide, mais ne remplace pas une mise à jour : après un correctif PHP, tous les processus PHP doivent redémarrer, il ne suffit pas de vider le cache.
Que fait exactement OPcache ?
À chaque requête, PHP doit lire un script, le décomposer et le traduire en opcodes, c'est-à-dire en instructions pour la machine virtuelle de la Zend Engine. Pour un CMS de plusieurs centaines de fichiers, c'est beaucoup de travail qui se répète sans cesse.
OPcache stocke le résultat de cette traduction dans la mémoire partagée (shared memory). Tous les workers d'un service PHP-FPM ou tous les processus Apache avec mod_php y accèdent. Si un fichier ne change pas, son bytecode est réutilisé directement.
OPcache se charge comme extension Zend, et non comme extension ordinaire :
; Linux (chemin selon le paquet)
zend_extension=opcache.so
; Windows
; zend_extension=php_opcache.dll
Selon le manuel, OPcache doit être chargé avant Xdebug lorsque les deux sont actifs.
Il importe de savoir à qui appartient la mémoire partagée. Avec PHP-FPM, le processus maître la crée et tous les workers de tous les pools de ce service l'utilisent. Avec mod_php, elle appartient au processus principal d'Apache. Un redémarrage du service vide donc entièrement le cache, alors qu'un simple nouveau worker ne le fait pas. En ligne de commande, il n'existe pas de mémoire partagée d'un appel à l'autre, car chaque appel est un processus distinct ; OPcache y est désactivé par défaut.
Pour les décideurs, une règle simple suffit : OPcache est presque toujours pertinent sur les serveurs de production, mais exige une procédure claire pour les déploiements et les mises à jour.
Quels réglages d'OPcache comptent ?
Les valeurs suivantes proviennent de la page de configuration du manuel PHP. La colonne « Modifiable » indique où un réglage peut être défini : INI_SYSTEM uniquement dans php.ini ou la configuration du serveur, INI_ALL partout.
| Directive | Par défaut | Modifiable | Signification |
|---|---|---|---|
opcache.enable |
1 | INI_ALL | cache activé ou désactivé, à l'exécution uniquement désactivable |
opcache.enable_cli |
0 | INI_SYSTEM | cache pour la ligne de commande |
opcache.memory_consumption |
128 | INI_SYSTEM | taille de la mémoire partagée en Mo |
opcache.interned_strings_buffer |
8 | INI_SYSTEM | mémoire pour les chaînes partagées en Mo |
opcache.max_accelerated_files |
10000 | INI_SYSTEM | nombre maximal de scripts en cache |
opcache.validate_timestamps |
1 | INI_ALL | vérifie si les fichiers ont été modifiés |
opcache.revalidate_freq |
2 | INI_ALL | intervalle de vérification en secondes, 0 = à chaque requête |
opcache.restrict_api |
vide | INI_SYSTEM | n'autorise les fonctions de l'API qu'à partir d'un chemin |
opcache.validate_permission |
0 | INI_SYSTEM | vérifie les droits sur les fichiers par rapport à l'utilisateur courant |
opcache.validate_root |
0 | INI_SYSTEM | évite les collisions de noms avec chroot |
opcache.file_cache |
vide | INI_SYSTEM | deuxième niveau de cache sur le disque |
Une configuration de production typique pourrait ressembler à ceci. Vérifiez au préalable les valeurs avec votre application :
opcache.enable=1
opcache.memory_consumption=256
opcache.interned_strings_buffer=16
opcache.max_accelerated_files=20000
opcache.validate_timestamps=1
opcache.revalidate_freq=60
opcache.restrict_api=/var/www/admin-tools
Avec opcache.validate_timestamps=0, on gagne un peu de performance. Mais OPcache ne détecte alors plus du tout les fichiers modifiés, et chaque déploiement exige une réinitialisation du cache ou un redémarrage.
Observer le cache et le vider de façon ciblée
OPcache fournit ses propres fonctions, dont opcache_get_status(), opcache_get_configuration(), opcache_invalidate() et opcache_reset(). Un bref coup d'œil, en lecture seule, au taux de remplissage :
<?php
$status = opcache_get_status(false);
if ($status === false) {
exit("OPcache ist nicht aktiv.\n");
}
printf("Gecachte Skripte: %d\n", $status['opcache_statistics']['num_cached_scripts']);
printf("Freier Speicher: %.1f MB\n", $status['memory_usage']['free_memory'] / 1048576);
Sur les serveurs hébergeant plusieurs sites, chaque application ne devrait pas pouvoir vider ou lire le cache de toutes les autres. C'est à cela que sert opcache.restrict_api : seuls les scripts situés sous le chemin indiqué peuvent appeler les fonctions de l'API.
JIT : le compilateur de code machine dans OPcache
Depuis PHP 8.0, OPcache contient un compilateur JIT qui traduit les opcodes en code machine. Selon le RFC sur le JIT, il constitue une partie presque autonome d'OPcache et place le code produit dans une zone distincte de la mémoire partagée. Il se pilote avec opcache.jit et opcache.jit_buffer_size. Depuis PHP 8.4.0, opcache.jit vaut disable par défaut. L'article JIT et performances dans PHP 8 explique quand le JIT vaut la peine.
OPcache et sécurité : ce qui s'applique après PHP2Shell
OPcache n'est pas un outil de sécurité, mais il a des conséquences pour la sécurité. Trois points comptent après PHP2Shell :
- Une réinitialisation du cache n'est pas une mise à jour.
opcache_reset()ne vide que les scripts en cache. L'interpréteur affecté par les failles CVE-2026-109618 et CVE-2026-109619 reste en mémoire jusqu'au redémarrage de tous les processus. La marche à suivre figure sous Redémarrer PHP-FPM et tous les workers. - Du code injecté peut survivre dans le cache. Si un fichier a été manipulé puis remplacé, l'ancienne version peut continuer à s'exécuter lorsque la vérification des horodatages est désactivée. Après un nettoyage, un redémarrage est donc nécessaire.
- La mémoire partagée exige une séparation. Sur les serveurs multi-clients, des pools PHP-FPM distincts par site ainsi que
opcache.validate_permissionetopcache.restrict_apiaident. Un répertoire pouropcache.file_cachene doit être accessible en écriture que par l'utilisateur PHP.
Après chaque mise à jour, vérifiez que l'heure de démarrage des processus PHP est postérieure à la mise à jour. Un OPcache plein avec un taux de réussite élevé ne prouve pas que le nouvel interpréteur tourne.
Comment PHP SOS aide-t-il concrètement ?
- Vérifier la configuration : nous relevons la configuration d'OPcache active pour chaque mode d'exécution et la comparons à votre application.
- Sécuriser les déploiements : nous mettons en place une procédure fixe de réinitialisation du cache et de redémarrage, afin qu'aucune ancienne version du code ne continue de tourner.
- Séparer les clients : dans le cadre du durcissement de serveur, nous configurons des pools distincts,
restrict_apiet des droits adaptés. - Contrôler après les mises à jour : nous prouvons, grâce aux heures de démarrage des processus, que l'interpréteur mis à jour est réellement actif.
Questions fréquentes
OPcache est-il actif par défaut ?
OPcache est livré avec PHP depuis PHP 5.5.0 et, selon le manuel, fonctionne par défaut avec opcache.enable=1 dès que l'extension est chargée. Qu'elle soit chargée dépend de votre paquet ou de votre hébergeur. En ligne de commande, en revanche, OPcache est désactivé par défaut (opcache.enable_cli=0).
Pourquoi vois-je encore l'ancien code après un déploiement ?
Vraisemblablement parce qu'OPcache ne vérifie les modifications de fichiers qu'à intervalles réguliers, voire jamais. Avec opcache.revalidate_freq, il attend par défaut deux secondes ; avec opcache.validate_timestamps=0, jamais. Seule une réinitialisation du cache ou un redémarrage de PHP-FPM ou d'Apache aide alors.
opcache_reset() suffit-il après une mise à jour de PHP ?
Non. opcache_reset() ne vide que le cache des scripts traduits. Une mise à jour de PHP remplace l'interpréteur lui-même, qui est chargé dans chaque processus en cours d'exécution. Pour cela, tous les processus PHP doivent être redémarrés.
Le JIT est-il activé dans PHP 8.4 ?
Selon le manuel, opcache.jit est réglé sur disable par défaut depuis PHP 8.4.0, tandis que opcache.jit_buffer_size vaut 64M. Auparavant, c'était l'inverse : jit sur tracing, tampon à 0. Dans les deux cas, le JIT n'est pas actif sans configuration délibérée.