Si vous voyez des TTFB qui oscillent entre 200 ms et 2 s selon les requêtes, avec des “premières visites” très lentes après un déploiement, OPcache est souvent le coupable… ou la solution mal configurée.
Le problème de performance
WordPress 6.9.4 exécute beaucoup de PHP par requête : chargement du core, plugins, thème, autoloaders, templates, REST API, etc. Sans OPcache (ou avec un OPcache sous-dimensionné), PHP re-parse et re-compile des centaines de fichiers, ce qui se traduit par :
- TTFB élevé (Time To First Byte), surtout sur pages non mises en cache, admin, endpoints REST, WooCommerce, etc.
- Variabilité : une requête rapide, la suivante lente (évictions OPcache, invalidations, redémarrages PHP-FPM).
- CPU inutile : compilation répétée au lieu d’exécuter du bytecode déjà en mémoire.
Impact concret : baisse des Core Web Vitals (notamment via le TTFB qui influence LCP), crawl budget SEO gaspillé, et une admin WordPress pénible (édition, recherche, constructeur de pages). J’ai souvent croisé ce problème sur des sites “optimisés” côté cache page/CDN, mais qui restent lents dès qu’on sort du cache (prévisualisations, REST, wp-admin, endpoints Elementor/Divi).
À la fin, vous saurez :
- dimensionner OPcache pour un WordPress moderne (PHP 8.1+), sans deviner,
- monitorer l’utilisation (hits/misses, mémoire, fragmentation) avec du code et des endpoints sûrs,
- éviter les invalidations massives après déploiement et les “cache warmups” inefficaces,
- valider les gains avec des mesures reproductibles (WP-CLI, logs, timings).
Résumé rapide
- OPcache doit être assez grand : mémoire + nombre de scripts (sinon évictions, misses, TTFB instable).
- Mesurez avant de toucher : hits/misses, “wasted memory”, “num_cached_scripts”, “opcache_get_status()”.
- Évitez revalidate trop agressif : un
revalidate_freqmal choisi peut soit servir du code périmé, soit invalider en boucle. - Ne publiez jamais un endpoint OPcache ouvert : fuite d’info (chemins, versions, scripts) = surface d’attaque.
- Attention au multi-PHP-FPM : OPcache est par worker pool, pas “global serveur”.
- Vérifiez l’effet sur TTFB avec des tests non-cachés (admin, REST, pages bypass cache).
Diagnostic avec du code
1) Vérifier si OPcache est actif (et sa config réelle)
Ne vous fiez pas à un article de 2019 ou à une capture d’écran. L’unique vérité, c’est opcache_get_configuration() et opcache_get_status() (si autorisé).
<?php
// Fichier: wp-content/mu-plugins/bpcab-opcache-diagnostic.php
// Objectif: exposer un diagnostic minimal via WP-CLI et logs, sans endpoint public.
if (!defined('ABSPATH')) {
exit;
}
add_action('init', function () {
// Ne faites rien côté front. On garde le diagnostic côté WP-CLI / admin uniquement.
}, 1);
if (defined('WP_CLI') && WP_CLI) {
WP_CLI::add_command('bpcab opcache:status', function () {
if (!function_exists('opcache_get_status')) {
WP_CLI::error('OPcache non disponible (extension non chargée).');
}
$status = opcache_get_status(false); // false = ne pas lister les scripts (moins verbeux)
$config = function_exists('opcache_get_configuration') ? opcache_get_configuration() : null;
WP_CLI::log('OPcache enabled: ' . (($status['opcache_enabled'] ?? false) ? 'yes' : 'no'));
WP_CLI::log('Memory used: ' . ($status['memory_usage']['used_memory'] ?? 0));
WP_CLI::log('Memory free: ' . ($status['memory_usage']['free_memory'] ?? 0));
WP_CLI::log('Wasted: ' . ($status['memory_usage']['wasted_memory'] ?? 0) . ' (pct: ' . ($status['memory_usage']['current_wasted_percentage'] ?? 0) . ')');
WP_CLI::log('Cached scripts: ' . ($status['opcache_statistics']['num_cached_scripts'] ?? 0));
WP_CLI::log('Max cached keys: ' . ($config['directives']['opcache.max_accelerated_files'] ?? 'n/a'));
WP_CLI::log('Hits: ' . ($status['opcache_statistics']['hits'] ?? 0));
WP_CLI::log('Misses: ' . ($status['opcache_statistics']['misses'] ?? 0));
WP_CLI::log('Restart pending: ' . (($status['restart_pending'] ?? false) ? 'yes' : 'no'));
WP_CLI::log('Restart in progress: ' . (($status['restart_in_progress'] ?? false) ? 'yes' : 'no'));
});
}
Exécution :
wp bpcab opcache:status
2) Mesurer le TTFB côté WordPress (sans outil externe)
Je préfère instrumenter au niveau MU-plugin, puis corréler avec vos logs Nginx/Apache. Voici un mini-profiler “temps WordPress” (sans dépendance) qui ajoute des en-têtes sur les réponses non-cachées.
<?php
// Fichier: wp-content/mu-plugins/bpcab-timing-headers.php
// Ajoute des headers de timing pour corréler OPcache et TTFB.
// Attention: ne faites pas ça sur un site public sans filtrer, vous divulguez des infos.
if (!defined('ABSPATH')) {
exit;
}
add_action('muplugins_loaded', function () {
$GLOBALS['bpcab_timing_start'] = hrtime(true);
}, 0);
add_action('shutdown', function () {
if (headers_sent()) {
return;
}
// Limitez aux admins connectés pour éviter la fuite d'information.
if (!is_user_logged_in() || !current_user_can('manage_options')) {
return;
}
$start = $GLOBALS['bpcab_timing_start'] ?? null;
if (!$start) {
return;
}
$elapsedMs = (hrtime(true) - $start) / 1e6;
header('X-WP-Elapsed-MS: ' . number_format($elapsedMs, 2, '.', ''));
header('X-WP-PHP: ' . PHP_VERSION);
if (function_exists('opcache_get_status')) {
$st = opcache_get_status(false);
$hits = $st['opcache_statistics']['hits'] ?? null;
$misses = $st['opcache_statistics']['misses'] ?? null;
if ($hits !== null && $misses !== null) {
header('X-OPcache-Hits: ' . (int) $hits);
header('X-OPcache-Misses: ' . (int) $misses);
}
}
}, 9999);
Ensuite, faites 10 requêtes sur une page admin lente (ex: édition d’un article) et comparez après ajustements OPcache. Avec Elementor/Divi/Avada, testez aussi un endpoint REST (la plupart des builders s’appuient dessus).
3) Query Monitor, slow query log et WP_DEBUG (pour ne pas se tromper de cible)
OPcache accélère la compilation/exécution PHP, pas des requêtes SQL catastrophiques. Avant d’investir du temps, vérifiez que vous êtes bien CPU/PHP-bound et pas DB-bound.
- Plugin Query Monitor (pour dev/staging) : wordpress.org/plugins/query-monitor
- Debug WordPress : developer.wordpress.org – Debugging
Exemple de configuration de debug (à activer temporairement) :
<?php
// wp-config.php (extrait) — à activer temporairement sur staging
define('WP_DEBUG', true);
define('WP_DEBUG_LOG', true);
define('WP_DEBUG_DISPLAY', false);
// Si vous faites des tests de perfs, évitez d'inonder l'écran et cassez le cache page le moins possible.
Pour MySQL/MariaDB, activez le slow query log côté serveur (pas dans WordPress) et corrélez les timestamps avec vos tests. OPcache ne corrigera pas une requête à 1,5 s sur wp_options.
4) WP-CLI : mesurer un “cold run” vs “warm run”
WP-CLI exécute WordPress en CLI, donc ce n’est pas un proxy exact du web. Mais c’est excellent pour observer l’impact “compilation” et repérer un OPcache absent côté CLI.
# Mesure simple (à répéter)
time wp option get siteurl --skip-plugins --skip-themes
# Mesure avec WP chargé comme en prod (plus réaliste)
time wp post list --post_type=page --fields=ID,post_title --format=table
Si le premier run est très lent et les suivants beaucoup plus rapides, vous avez un effet “warmup” (OPcache ou caches applicatifs). Si rien ne change, OPcache est peut-être inactif sur ce SAPI (CLI vs FPM), ou votre bottleneck est ailleurs.
Étape 1 : dimensionner OPcache pour WordPress
Le dimensionnement, c’est 80% du résultat. Sur WordPress, l’erreur classique : opcache.memory_consumption trop bas et opcache.max_accelerated_files ridiculement petit. Résultat : OPcache évince des scripts, vous avez des misses, et le TTFB devient imprévisible.
Code AVANT (lent) : config typique sous-dimensionnée
; php.ini (exemple réel vu sur des mutualisés)
opcache.enable=1
opcache.memory_consumption=64
opcache.interned_strings_buffer=4
opcache.max_accelerated_files=4000
opcache.revalidate_freq=2
Avec WordPress 6.9.4 + un builder (Divi 5 / Elementor) + 20-40 plugins, 64 MB est souvent trop bas, et 4000 fichiers accélérés peut être limite selon votre stack (vendor/, libs, packages).
APRÈS (optimisé) : config “baseline” fiable
Voici une base que j’applique souvent sur des VPS/VM. Ajustez ensuite avec le monitoring.
; php.ini / conf.d/opcache.ini (baseline 2026, PHP 8.1+)
opcache.enable=1
opcache.enable_cli=0
; Mémoire: 128 à 256 MB pour WordPress “standard”, 256 à 512 MB si gros site / Woo / builders lourds
opcache.memory_consumption=256
; Chaînes internées: utile avec beaucoup de symboles/classes (plugins/builders)
opcache.interned_strings_buffer=16
; Nombre de scripts: montez large pour éviter les évictions
opcache.max_accelerated_files=20000
; Bon compromis: vérifie périodiquement les timestamps
; Sur des déploiements atomiques (symlink), vous pouvez monter plus haut.
opcache.revalidate_freq=30
opcache.validate_timestamps=1
; Réduit les risques de "file cache stampede" sur certains FS
opcache.file_update_protection=2
; Limite de taille d'un fichier PHP mis en cache (0 = illimité)
opcache.max_file_size=0
; Protection contre la fragmentation excessive
opcache.max_wasted_percentage=10
Pourquoi c’est plus rapide
- Plus de scripts en cache ⇒ moins de misses ⇒ moins de compilation ⇒ CPU plus stable.
- Moins d’évictions ⇒ moins de “cold paths” en pleine charge.
- interned_strings_buffer réduit la duplication de chaînes (noms de classes, fonctions, chemins), utile sur des stacks très orientées OOP.
Mesure d’impact (reproductible)
Le gain se voit surtout sur :
- requêtes non-cachées (admin, REST, pages bypass cache, preview),
- premier hit après reload PHP-FPM,
- pics de charge où OPcache évite une explosion CPU.
Avec les headers de timing plus haut, vous cherchez typiquement :
- baisse de X-WP-Elapsed-MS sur admin/REST (souvent 10–40% selon site),
- stabilisation : moins de variance entre requêtes.
Étape 2 : activer le monitoring OPcache et exposer des metrics
Le monitoring “à la main” via phpinfo() finit toujours mal (endpoint oublié, accessible publiquement). Je préfère : WP-CLI + endpoint REST protégé + logs.
1) Endpoint REST WordPress (protégé) pour lire l’état OPcache
Ce endpoint renvoie un sous-ensemble non sensible. Il est réservé aux administrateurs authentifiés. Ne l’exposez pas derrière un cache public.
<?php
// Fichier: wp-content/mu-plugins/bpcab-opcache-rest.php
// Endpoint REST sécurisé pour monitorer OPcache.
// Risque: fuite d'infos si vous l'ouvrez. Gardez une permission stricte.
if (!defined('ABSPATH')) {
exit;
}
add_action('rest_api_init', function () {
register_rest_route('bpcab/v1', '/opcache', [
'methods' => 'GET',
'permission_callback' => function () {
return is_user_logged_in() && current_user_can('manage_options');
},
'callback' => function () {
if (!function_exists('opcache_get_status')) {
return new WP_Error('opcache_unavailable', 'OPcache non disponible.', ['status' => 501]);
}
$st = opcache_get_status(false);
$payload = [
'enabled' => (bool) ($st['opcache_enabled'] ?? false),
'memory' => [
'used' => (int) ($st['memory_usage']['used_memory'] ?? 0),
'free' => (int) ($st['memory_usage']['free_memory'] ?? 0),
'wasted' => (int) ($st['memory_usage']['wasted_memory'] ?? 0),
'wasted_pct' => (float) ($st['memory_usage']['current_wasted_percentage'] ?? 0),
],
'stats' => [
'cached_scripts' => (int) ($st['opcache_statistics']['num_cached_scripts'] ?? 0),
'hits' => (int) ($st['opcache_statistics']['hits'] ?? 0),
'misses' => (int) ($st['opcache_statistics']['misses'] ?? 0),
'hit_rate' => (float) ($st['opcache_statistics']['opcache_hit_rate'] ?? 0),
],
'restarts' => [
'oom' => (int) ($st['opcache_statistics']['oom_restarts'] ?? 0),
'hash' => (int) ($st['opcache_statistics']['hash_restarts'] ?? 0),
'manual' => (int) ($st['opcache_statistics']['manual_restarts'] ?? 0),
],
'php' => PHP_VERSION,
'timestamp' => time(),
];
return rest_ensure_response($payload);
},
]);
});
Test :
# En étant connecté (cookie) ou via Application Passwords (recommandé)
curl -s https://example.com/wp-json/bpcab/v1/opcache | jq
Si vous utilisez des Application Passwords pour interroger l’API (monitoring externe), document officiel : developer.wordpress.org – REST API Authentication.
2) Export Prometheus (option avancée)
Si vous avez Prometheus, exposez des métriques en texte. Même principe : endpoint protégé, ou mieux derrière un VPN / IP allowlist au niveau serveur.
<?php
// Fichier: wp-content/mu-plugins/bpcab-opcache-prometheus.php
// Endpoint REST "metrics" au format Prometheus (texte).
// Ne l'ouvrez pas publiquement.
if (!defined('ABSPATH')) {
exit;
}
add_action('rest_api_init', function () {
register_rest_route('bpcab/v1', '/metrics', [
'methods' => 'GET',
'permission_callback' => function () {
// Exemple: réserver à un admin. Pour du vrai monitoring, préférez une allowlist IP côté serveur.
return is_user_logged_in() && current_user_can('manage_options');
},
'callback' => function () {
if (!function_exists('opcache_get_status')) {
return new WP_Error('opcache_unavailable', 'OPcache non disponible.', ['status' => 501]);
}
$st = opcache_get_status(false);
$lines = [];
$lines[] = '# TYPE php_opcache_enabled gauge';
$lines[] = 'php_opcache_enabled ' . (((bool) ($st['opcache_enabled'] ?? false)) ? '1' : '0');
$used = (int) ($st['memory_usage']['used_memory'] ?? 0);
$free = (int) ($st['memory_usage']['free_memory'] ?? 0);
$wasted = (int) ($st['memory_usage']['wasted_memory'] ?? 0);
$lines[] = '# TYPE php_opcache_memory_used_bytes gauge';
$lines[] = 'php_opcache_memory_used_bytes ' . $used;
$lines[] = '# TYPE php_opcache_memory_free_bytes gauge';
$lines[] = 'php_opcache_memory_free_bytes ' . $free;
$lines[] = '# TYPE php_opcache_memory_wasted_bytes gauge';
$lines[] = 'php_opcache_memory_wasted_bytes ' . $wasted;
$cached = (int) ($st['opcache_statistics']['num_cached_scripts'] ?? 0);
$hits = (int) ($st['opcache_statistics']['hits'] ?? 0);
$misses = (int) ($st['opcache_statistics']['misses'] ?? 0);
$hitRate = (float) ($st['opcache_statistics']['opcache_hit_rate'] ?? 0);
$lines[] = '# TYPE php_opcache_cached_scripts gauge';
$lines[] = 'php_opcache_cached_scripts ' . $cached;
$lines[] = '# TYPE php_opcache_hits_total counter';
$lines[] = 'php_opcache_hits_total ' . $hits;
$lines[] = '# TYPE php_opcache_misses_total counter';
$lines[] = 'php_opcache_misses_total ' . $misses;
$lines[] = '# TYPE php_opcache_hit_rate gauge';
$lines[] = 'php_opcache_hit_rate ' . $hitRate;
return new WP_REST_Response(implode("n", $lines) . "n", 200, [
'Content-Type' => 'text/plain; version=0.0.4; charset=utf-8',
'Cache-Control' => 'no-store, no-cache, must-revalidate, max-age=0',
]);
},
]);
});
Étape 3 : éviter les invalidation storms et redeploys douloureux
Le scénario classique : vous déployez (ou mettez à jour un plugin), et pendant 2–10 minutes le site “rame”. En coulisses, OPcache invalide des centaines de fichiers, puis les workers recompilent au fil de l’eau. Sous charge, ça devient un mini DDoS… auto-infligé.
1) Ajuster validate_timestamps / revalidate_freq selon votre méthode de déploiement
- Déploiement “classique” (rsync/FTP qui modifie des fichiers en place) : gardez
validate_timestamps=1et unrevalidate_freqraisonnable (10–60 s). - Déploiement atomique (nouveau répertoire + symlink) : vous pouvez monter
revalidate_freq(voire utiliservalidate_timestamps=0si vous maîtrisez la purge OPcache), mais c’est une décision ops, pas un tweak “blog”.
Si vous mettez validate_timestamps=0 sans stratégie de purge, vous servirez du code périmé après update. Je l’ai vu sur des sites WordPress où l’admin “voyait” l’update, mais le front exécutait encore l’ancienne version (et les bugs étaient impossibles à reproduire).
2) Pré-chauffer (warmup) intelligemment après déploiement
Un warmup utile cible des URLs non-cachées et des endpoints utilisés par vos visiteurs / éditeurs, pas seulement la home.
# Exemple de warmup minimal (à adapter) : home + page + REST + admin-ajax
# Utilisez un user-agent dédié pour filtrer dans les logs.
BASE="https://example.com"
UA="OPcacheWarmup/1.0"
curl -A "$UA" -sS "$BASE/" > /dev/null
curl -A "$UA" -sS "$BASE/?nocache=1" > /dev/null
# REST API (souvent sollicitée par Elementor/Divi/Avada)
curl -A "$UA" -sS "$BASE/wp-json/wp/v2/types" > /dev/null
# Admin-ajax (à éviter si lourd, mais utile pour tester)
curl -A "$UA" -sS "$BASE/wp-admin/admin-ajax.php?action=heartbeat" > /dev/null
Edge case : si vous avez un cache page agressif (Varnish, plugin, Cloudflare), votre warmup peut ne jamais toucher PHP. Dans ce cas, testez des URLs qui bypassent le cache (cookie admin, querystring ignoré, header spécifique). Sinon vous “chauffez” juste le CDN.
3) Ne pas purger OPcache depuis WordPress en production (sauf cas contrôlé)
Oui, il existe opcache_reset(). Non, je ne recommande pas un bouton “Reset OPcache” dans wp-admin sur un site à trafic. Ça force une recompilation massive sur les prochaines requêtes. Sous charge, vous venez de créer un trou de perf.
Si vous devez purger, faites-le :
- hors pic,
- avec un warmup derrière,
- pool par pool (PHP-FPM),
- et idéalement via un outil ops (reload FPM progressif) plutôt qu’un clic dans l’admin.
Étape 4 : OPcache et CLI (WP-CLI, cron, workers)
Beaucoup d’équipes mesurent avec WP-CLI puis concluent “OPcache ne sert à rien”. Souvent, OPcache est désactivé en CLI (opcache.enable_cli=0) — ce qui est fréquent et parfois volontaire.
1) Décider pour opcache.enable_cli
- enable_cli=0 : évite de consommer de la mémoire pour des commandes ponctuelles. Très bien si WP-CLI est rare.
- enable_cli=1 : utile si vous avez des workers CLI longs (queues, importeurs, cron via CLI) et que vous voulez réduire le CPU.
Si vous activez enable_cli, sachez que OPcache en CLI n’est pas “partagé” avec FPM. Ce sont des contextes séparés.
2) Mesurer cron et tâches longues
Pour des sites avec WooCommerce, des imports, ou des builders qui génèrent des CSS/JSON, les tâches cron peuvent compiler beaucoup de PHP.
# Mesure d'un cron "manuel" (exemple)
time wp cron event run --due-now
# Voir si la charge se stabilise après warm runs
time wp cron event run --due-now
Configuration serveur
php.ini / PHP-FPM (recommandations concrètes)
Sur PHP-FPM, OPcache est par pool. Si vous avez plusieurs pools (sites multiples), dimensionnez par pool, pas “par serveur”.
; /etc/php/8.3/fpm/conf.d/10-opcache.ini (exemple, adaptez la version)
opcache.enable=1
opcache.memory_consumption=256
opcache.interned_strings_buffer=16
opcache.max_accelerated_files=20000
opcache.validate_timestamps=1
opcache.revalidate_freq=30
opcache.file_update_protection=2
opcache.max_wasted_percentage=10
; JIT: souvent neutre ou négatif sur WordPress (workload I/O + appels dynamiques).
; Si vous testez, faites-le avec mesure. Par défaut, je le laisse off.
opcache.jit=0
opcache.jit_buffer_size=0
Référence officielle OPcache : php.net – OPcache et directives : php.net – OPcache configuration.
Apache/.htaccess : ce que vous pouvez (et ne pouvez pas) faire
OPcache se configure au niveau PHP (ini). Dans un .htaccess, vous pouvez parfois définir des valeurs via php_value si vous êtes en mod_php. Sur PHP-FPM, ça ne marche généralement pas et ça pousse les gens à croire que “c’est appliqué” alors que non.
# .htaccess (Apache + mod_php uniquement) — à éviter si vous n'êtes pas sûr
# php_value opcache.memory_consumption 256
# php_value opcache.max_accelerated_files 20000
Dans mon expérience, la plupart des configs modernes (Apache + FPM, ou Nginx + FPM) doivent être faites dans les fichiers ini du pool FPM ou du conteneur.
wp-config.php : éviter les faux amis
Il n’existe pas de constante WordPress magique pour “activer OPcache”. Si vous voyez un snippet qui définit OPCACHE_ENABLED ou autre, c’est du folklore.
Ce que vous pouvez faire côté WordPress : instrumenter, monitorer, et éviter les patterns qui détruisent l’efficacité (autoloaders qui scannent le disque, includes dynamiques non nécessaires).
Nginx / FastCGI cache / CDN : interactions à connaître
- OPcache accélère PHP, même si vous avez un cache page : admin/REST restent des requêtes PHP.
- Si votre cache page sert 95% du trafic, OPcache devient critique pour les 5% restants (qui sont souvent les plus chers).
- Avec Cloudflare/CDN, testez toujours des requêtes qui touchent PHP (bypass cache), sinon vous mesurez le CDN.
Pour les Core Web Vitals, le TTFB côté CDN peut être excellent alors que l’admin est lente. Les deux peuvent coexister. Votre objectif ici : rendre WordPress “réactif” hors cache.
Vérification des résultats
1) Vérifier les métriques OPcache
Après 10–20 minutes de trafic normal (ou un warmup), vous voulez typiquement :
- hit rate élevé (souvent > 95% sur un site stable),
- num_cached_scripts proche de votre réalité, mais sans toucher le plafond
max_accelerated_files, - free_memory non ridicule (évitez d’être à 0–5 MB libres),
- wasted_pct bas et stable (sinon fragmentation / redémarrages).
Commande :
wp bpcab opcache:status
2) Vérifier la perf WordPress (timings + logs)
Avec le MU-plugin de headers, faites une série de requêtes identiques :
# Exemple: page admin (nécessite cookie/session), sinon testez une URL bypass cache.
curl -I -s https://example.com/wp-json/wp/v2/types | grep -E "X-WP-Elapsed-MS|X-OPcache"
Si vous avez Nginx, activez temporairement le log du temps upstream pour corréler :
# Exemple de format de log (nginx.conf) - extrait indicatif
# log_format main '$remote_addr - $request $status '
# 'rt=$request_time urt=$upstream_response_time '
# 'uht=$upstream_header_time';
3) Core Web Vitals : test “hors cache”
Pour relier OPcache à CWV, testez des pages dynamiques ou bypass cache, et regardez le TTFB. Le but n’est pas de “gagner 2 points PageSpeed”, mais de supprimer les pics.
Si les performances ne s’améliorent pas
Quand OPcache est bien dimensionné et que ça reste lent, le goulot est souvent ailleurs. Voici une check-list orientée code/infra, dans l’ordre où je la déroule en audit.
1) Confirmer que vous testez bien le même chemin (cache page/CDN)
- Testez une URL qui bypass cache (cookie admin, querystring non ignoré, header), sinon vous mesurez le CDN.
- Videz les caches dans le bon ordre : plugin cache, cache serveur (FastCGI/Varnish), CDN, puis navigateur. J’ai déjà vu des gens “optimiser OPcache” alors qu’ils servaient une page HTML statique.
2) Vérifier les redémarrages PHP-FPM
Si PHP-FPM redémarre souvent (OOM, reloads, limites), OPcache “repart de zéro” régulièrement. Cherchez :
- OOM killer (dmesg),
pm.max_childrentrop élevé,- limites mémoire conteneur (Kubernetes/Compose) trop basses.
3) DB lente ou options autoload énormes
OPcache ne corrige pas :
- un
wp_optionsautoload à plusieurs dizaines de MB, - des requêtes non indexées,
- des appels HTTP sortants lents (API externes).
Mesure rapide via Query Monitor (staging) + slow query log. Référence WordPress pour l’optimisation : developer.wordpress.org – Performance.
4) Plugins qui scannent le disque à chaque requête
Certains plugins/builders font des glob(), file_exists() massifs, ou reconstruisent des caches à la volée. OPcache n’accélère pas l’I/O disque. Si votre FS est lent (NFS, stockage réseau), vous verrez peu de gains.
5) Incompatibilités et code d’anciens tutoriels
Si vous avez copié un snippet “OPcache + WordPress” d’une époque PHP 7.x, vérifiez qu’il ne fait pas :
- un
opcache_reset()à chaque déploiement via hook WordPress mal choisi, - un endpoint non authentifié,
- une logique basée sur des constantes inexistantes.
Pièges et erreurs courantes
| Symptôme | Cause probable | Vérification | Solution |
|---|---|---|---|
| TTFB très variable, pics après quelques minutes | OPcache sous-dimensionné (évictions), max_accelerated_files trop bas |
num_cached_scripts proche du max, hit rate qui baisse, misses qui montent |
Augmenter memory_consumption et max_accelerated_files, puis re-mesurer |
| Site lent après déploiement pendant 5–10 min | Invalidations + recompilation sous charge | Logs montrent hausse CPU + latence, OPcache misses bondissent | Warmup ciblé, ajuster revalidate_freq, déploiement atomique si possible |
| Update plugin “faite”, mais le code exécuté semble ancien | validate_timestamps=0 sans purge OPcache |
Comparer version plugin sur disque vs comportement, vérifier directives OPcache | Remettre validate_timestamps=1 ou mettre en place une purge contrôlée |
| Le monitoring OPcache divulgue des chemins/infos | Endpoint REST non protégé / phpinfo exposé | Tester en navigation privée/non connecté | Permission stricte, allowlist IP, supprimer phpinfo en prod |
| Vous “changez php.ini” mais rien ne bouge | Modification au mauvais endroit (pool FPM différent, ini non chargé) | phpinfo() en staging, ou opcache_get_configuration() via WP-CLI |
Éditer le bon fichier ini du bon SAPI/pool, reload FPM proprement |
| Erreur 500 après ajout d’un MU-plugin | Parenthèse/point-virgule oublié, ou code copié dans un plugin de snippets qui ajoute du wrapper | Consulter wp-content/debug.log et logs PHP |
Valider la syntaxe, déployer via MU-plugin simple, tests sur staging |
| Metrics incohérentes entre deux requêtes | Plusieurs pools/instances PHP-FPM derrière un load balancer | Comparer phpversion(), ajouter un header instance, vérifier LB |
Monitorer par instance/pool, sticky sessions pour tests |
| Admin lente avec Elementor/Divi/Avada malgré OPcache | REST/SQL lourd, appels externes, génération dynamique | Query Monitor, slow query log, profiler applicatif | Optimiser requêtes, transients, object cache, réduire I/O disque |
Erreurs “humaines” que je vois souvent
- Copier le code MU-plugin dans
functions.phpdu thème : à la première mise à jour de thème, tout disparaît. Et si le thème ne charge pas (fatal), vous perdez l’accès. - Tester sur production sans sauvegarde ni plan de rollback : une faute de syntaxe dans un MU-plugin = écran blanc immédiat.
- Utiliser un hook trop tard : si vous mesurez après
shutdownsans capturer le début, vos timings sont faux. - Oublier de vider le cache navigateur ou de bypass le CDN : vous mesurez du HTML statique et vous concluez “OPcache ne change rien”.
Conseils de maintenance
1) Mettre des seuils d’alerte simples
Avec l’endpoint REST/metrics :
- wasted_pct > 10–15% de façon persistante : fragmentation, envisagez un redémarrage contrôlé du pool hors pic.
- oom_restarts > 0 : OPcache manque de mémoire, augmentez
memory_consumption. - hit_rate qui chute : évictions, redémarrages FPM, ou déploiements trop fréquents.
2) Standardiser le déploiement
OPcache devient beaucoup plus prévisible avec :
- déploiements atomiques (symlink),
- warmup automatisé,
- reload FPM progressif si nécessaire (rolling).
3) Ne mélangez pas optimisation OPcache et “optimisation WordPress”
OPcache est une brique. Sur un WordPress avancé, le trio qui donne des résultats stables est :
- OPcache correctement dimensionné,
- object cache persistant (Redis/Memcached) pour réduire SQL et calcul,
- cache page/CDN pour absorber le trafic anonyme.
Pour l’object cache, référence : developer.wordpress.org – Object caching.
4) Compatibilité Divi 5 / Elementor / Avada
Ces builders augmentent souvent :
- le nombre de fichiers PHP chargés (frameworks internes, vendor),
- les endpoints REST sollicités,
- la charge admin.
OPcache aide particulièrement sur l’admin et les endpoints REST. Pour vos tests, incluez :
- édition d’une page builder,
- prévisualisation,
- chargement de la bibliothèque de templates,
- une requête REST typique (types, templates, autosaves).
Ressources
- PHP Manual – OPcache
- PHP Manual – OPcache configuration directives
- WordPress Developer Resources – Performance
- WordPress Developer Resources – Debugging in WordPress
- Query Monitor (plugin officiel sur WordPress.org)
- WordPress Developer Resources – REST API Handbook
- GitHub – WordPress/wordpress-develop (source du core)
- WordPress Core Trac (tickets et historique)
FAQ
OPcache améliore-t-il un site déjà derrière un cache page ?
Oui, parce que l’admin, le REST, les previews, les endpoints dynamiques (et tout ce qui bypass cache) exécutent toujours PHP. OPcache stabilise le CPU et réduit le TTFB sur ces chemins.
Quelle valeur pour opcache.memory_consumption sur WordPress 6.9.4 ?
En 2026, je pars rarement sous 128 MB. 256 MB est un bon baseline pour un site avec builder et plusieurs plugins. Pour un gros WooCommerce, 512 MB n’est pas choquant si vous avez la RAM.
Que faire si opcache.max_accelerated_files est trop bas ?
Montez-le (ex: 20000). Ensuite vérifiez num_cached_scripts et la stabilité du hit rate. Si vous êtes proche du plafond, vous êtes encore trop bas.
Dois-je activer OPcache JIT pour WordPress ?
Souvent non. WordPress est très I/O et appels dynamiques, le JIT n’apporte pas systématiquement de gain et peut compliquer le debugging. Si vous testez, faites-le sur staging avec des scénarios réalistes et des mesures.
Puis-je purger OPcache depuis wp-admin ?
Techniquement oui (via opcache_reset()), mais je l’évite sur un site à trafic : vous forcez une recompilation massive. Préférez une stratégie ops (reload FPM contrôlé) et un warmup.
Pourquoi mes métriques changent selon la requête ?
Souvent parce que vous avez plusieurs instances PHP-FPM derrière un load balancer. Chaque instance a son OPcache. Ajoutez un identifiant d’instance dans les headers/logs pour corréler.
OPcache est-il partagé entre PHP-FPM et WP-CLI ?
Non. Ce sont des SAPIs différentes. Même si vous activez opcache.enable_cli=1, vous aurez un cache séparé.
Mon host dit “OPcache activé”, mais je ne vois aucun gain
Deux causes fréquentes : OPcache trop petit (évictions) ou vous testez des pages servies par le CDN/cache page (donc pas de PHP). Mesurez sur admin/REST et vérifiez hits/misses.
Est-ce risqué d’exposer opcache_get_status via REST ?
Oui si mal protégé. Vous pouvez fuiter des informations sur votre stack. Gardez une permission stricte, désactivez toute mise en cache publique, et envisagez une allowlist IP au niveau serveur.
Quel est le “meilleur” revalidate_freq ?
Ça dépend de votre méthode de déploiement. 30 s est un compromis courant. Si vous déployez souvent et modifiez des fichiers en place, trop haut peut servir du code ancien. Si vous déployez en atomique, vous pouvez monter, mais seulement avec une stratégie de purge/reload.
OPcache peut-il empirer les performances ?
Oui, indirectement : si vous mettez une mémoire trop basse, vous créez des évictions et de la variance. Ou si vous purgez souvent (reset/restart), vous forcez des cold starts sous charge.