Cómo configurar OPcache en PHP en producción para ganar rendimiento
Guía técnica para configurar OPcache PHP en producción y obtener rendimiento real: parámetros, memoria, validación y solución de problemas.
OPcache es la extensión de caché de bytecode integrada en PHP desde la versión 5.5. Sin ella, PHP lee, parsea y compila cada archivo .php en cada petición — trabajo que se repite millones de veces al día en producción. Con OPcache habilitado y bien configurado, el bytecode compilado vive en memoria compartida y cada request reutiliza el resultado, recortando latencia y uso de CPU de forma significativa.
Este tutorial es para desarrolladores PHP que ejecutan aplicaciones en producción (Laravel, Symfony, WordPress o código propio) y aún no han habilitado OPcache, o lo habilitaron con configuración por defecto y no saben si está bien dimensionado. Tiempo estimado de ejecución: 20-30 minutos, incluyendo la parte de medición y ajuste.
El foco es práctico: parámetros que importan, valores razonables para empezar, cómo verificar que la caché realmente se está usando y cómo invalidar bytecode tras un deploy sin tumbar PHP-FPM. Todo probado en PHP 8.3 sobre Ubuntu 24.04 LTS, pero los principios sirven para cualquier versión 7.4+ en cualquier distribución.
Prerrequisitos
Servidor Linux con PHP 7.4 o superior, acceso sudo, PHP-FPM ejecutándose (o mod_php en Apache) y tu aplicación ya en producción. OPcache viene integrado en PHP desde 5.5 — no necesitas instalar un paquete separado en la mayoría de las distros. Ten acceso al php.ini o al directorio conf.d.
7.4 8.3 256-512 MB conf.d/10-opcache.ini Confirma que OPcache está disponible antes de configurar. Si el comando de abajo no lista opcache, necesitas instalar el paquete php-opcache (Debian/Ubuntu) o php-opcache (RHEL/Alma/Rocky).
php -m | grep -i opcache
Si la salida es Zend OPcache, está disponible y solo falta habilitarlo y ajustarlo. Si viene vacía, instálalo primero.
Verificar el estado actual
Antes de cambiar cualquier parámetro, registra cómo está OPcache ahora. Esto se convierte en tu línea base para comparar después.
Lista la configuración actual de OPcache:
php --ri opcacheLa salida muestra cada directiva y el valor efectivo. Busca opcache.enable, opcache.memory_consumption, opcache.max_accelerated_files y opcache.validate_timestamps. Si enable está en Off, la caché no está corriendo — explica buena parte de la lentitud.
Mira el estado en runtime vía PHP-FPM (no CLI):
Crea un archivo temporal /var/www/opcache-status.php con:
<?php
header('Content-Type: application/json');
echo json_encode(opcache_get_status(false), JSON_PRETTY_PRINT);Accede vía curl interno:
curl http://127.0.0.1/opcache-status.php | head -30Anota los valores de memory_usage.used_memory, memory_usage.free_memory, opcache_statistics.hits y opcache_statistics.misses. Si hits es cero o muy bajo comparado con misses, la caché está mal configurada o recién iniciada.
Borra el archivo después — no lo dejes expuesto en producción.
Configuración recomendada
Crea o edita el archivo de configuración específico de OPcache. En Ubuntu/Debian está en /etc/php/8.3/fpm/conf.d/10-opcache.ini (ajusta la versión).
Edita el archivo de configuración:
sudo nano /etc/php/8.3/fpm/conf.d/10-opcache.iniReemplaza el contenido por una configuración de producción sólida:
zend_extension=opcache.so
opcache.enable=1
opcache.enable_cli=0
opcache.memory_consumption=256
opcache.interned_strings_buffer=16
opcache.max_accelerated_files=20000
opcache.validate_timestamps=1
opcache.revalidate_freq=60
opcache.save_comments=1
opcache.fast_shutdown=1
opcache.huge_code_pages=0
opcache.max_wasted_percentage=10
opcache.consistency_checks=0Cada parámetro tiene un motivo específico — explicación debajo de la tabla de referencia.
Referencia de los parámetros principales:
| Directiva | Valor sugerido | Qué hace |
|---|---|---|
opcache.memory_consumption | 256 | Memoria compartida (MB) para bytecode |
opcache.interned_strings_buffer | 16 | Buffer (MB) para strings deduplicadas |
opcache.max_accelerated_files | 20000 | Cuántos archivos pueden estar cacheados |
opcache.validate_timestamps | 1 | Si es 0, nunca revalida — requiere reset manual |
opcache.revalidate_freq | 60 | Intervalo (s) entre verificaciones de mtime |
opcache.save_comments | 1 | Conserva DocBlocks — obligatorio para Laravel/Symfony |
opcache.fast_shutdown | 1 | Desasignación más rápida al final del request |
Los frameworks modernos (Laravel, Symfony, Doctrine) usan annotations y atributos PHP 8 que leen DocBlocks en runtime. Poner opcache.save_comments=0 rompe los containers de DI, validation y ORM mapping. La ganancia de memoria no compensa el riesgo.
Aplica la configuración reiniciando PHP-FPM:
sudo systemctl restart php8.3-fpmTras el reinicio, espera unos segundos para que los requests reales calienten la caché. En hosts con Apache + mod_php, usa sudo systemctl restart apache2.
Dimensionar memory_consumption correctamente
256 MB es un punto de partida razonable, pero la memoria ideal depende del tamaño de tu codebase. Una aplicación Laravel típica con vendor/ completo consume 80-150 MB. WordPress con 30 plugins puede pasar de 200 MB.
Mide el consumo real después de que la caché caliente (deja la app correr durante 10-15 minutos con tráfico normal):
php -r 'print_r(opcache_get_status(false)["memory_usage"]);'La salida se parece a:
Array
(
[used_memory] => 142336512
[free_memory] => 125829120
[wasted_memory] => 0
[current_wasted_percentage] => 0
)Si free_memory cae por debajo del 20% del total asignado, sube opcache.memory_consumption a 384 o 512 MB. Si wasted_memory supera el 10%, considera ajustar opcache.max_wasted_percentage o programar un reset en horarios de bajo tráfico.
Los frameworks orientados a objetos crean muchos nombres de clase, método y propiedad repetidos. El buffer de interned strings deduplica eso. En apps Laravel grandes, subir de 8 MB a 16 o 32 MB reduce fragmentación y libera memoria del bytecode principal.
Estrategia de deploy: invalidar la caché
Con validate_timestamps=1 y revalidate_freq=60, los cambios en archivos PHP solo se detectan después de 60 segundos. Para deploys atómicos, eso es insuficiente — necesitas invalidar la caché en el momento del deploy.
Instala la herramienta cachetool para invalidar la caché sin reiniciar PHP-FPM:
curl -sO https://gordalina.github.io/cachetool/downloads/cachetool.phar
chmod +x cachetool.phar
sudo mv cachetool.phar /usr/local/bin/cachetoolAñádelo a tu script de deploy, tras el git pull o rsync:
cachetool opcache:reset --fcgi=/var/run/php/php8.3-fpm.sockEl reset es instantáneo y no corta conexiones activas — a diferencia de systemctl reload.
El modo validate_timestamps=0 es más performante (cero stat() por request), pero si te olvidas de invalidar la caché tras el deploy, el servidor sirve código antiguo indefinidamente. Solo usa 0 si tu script de deploy tiene opcache_reset() o cachetool opcache:reset garantizado.
Verificar la ganancia real
Configurar sin medir es fe, no ingeniería. Compara latencia y CPU antes y después.
Corre un benchmark simple con ab (Apache Bench) o wrk contra un endpoint representativo:
ab -n 1000 -c 10 https://tudominio.com/api/productosCompara el Time per request antes y después de habilitar OPcache. Reducciones del 30-60% en apps Laravel/Symfony son comunes. En apps que dependen mucho de I/O (base de datos, APIs externas), la ganancia porcentual es menor porque PHP no es el cuello de botella.
Confirma que los hits están dominando a los misses:
php -r 'print_r(opcache_get_status(false)["opcache_statistics"]);'La relación hits / (hits + misses) debe superar el 99% después de que la caché caliente. Si queda por debajo del 95%, probablemente max_accelerated_files está bajo o la caché se está reseteando con frecuencia.
Solución de problemas
La caché se llena rápido y wasted_memory se dispara
Señal de que max_accelerated_files está bajo o que max_wasted_percentage fue alcanzado. OPcache empieza a descartar entradas y refragmenta la memoria. Aumenta max_accelerated_files a 30000-50000 y memory_consumption en un 50%.
opcache_get_status() retorna false en scripts CLI
Default seguro: opcache.enable_cli=0 deshabilita OPcache en CLI. Si necesitas inspeccionar vía CLI, súbelo a 1 temporalmente. En producción, mantén 0 — los scripts CLI son cortos y la caché no ayuda.
Los cambios en el código no aparecen tras el deploy
O validate_timestamps=0 sin reset programático, o una caché de otro tipo (Redis/file cache de la aplicación) está sirviendo datos antiguos. Ejecuta cachetool opcache:reset y limpia la caché de aplicación (php artisan cache:clear en Laravel, por ejemplo).
Próximos pasos
Una vez que OPcache esté estable, la siguiente ganancia suele venir de afinar PHP-FPM (pool size, pm.max_children) para acompañar el throughput mayor que el servidor consigue ahora. También vale la pena evaluar OPcache JIT en apps con componente CPU-intensivo y revisar headers de caché HTTP en nginx para reducir aún más la presión sobre PHP.
Si estás corriendo en hardware compartido y la ganancia de rendimiento se topa con el límite de recursos del plan, un VPS Hostini con KVM dedicado te da control total sobre PHP-FPM, memoria de OPcache y parámetros del kernel — sin disputa de CPU con vecinos ruidosos.
Preguntas frecuentes
¿Vale la pena OPcache para aplicaciones pequeñas con poco tráfico?
Sí. Incluso con bajo RPS, OPcache elimina el trabajo repetido de parseo y compilación en cada request. La ganancia porcentual de latencia es igual o mayor en apps pequeñas — solo el impacto absoluto es menor. Habilitarlo es prácticamente gratis: unos MB de RAM a cambio de menos CPU.
¿Cuál es la diferencia entre opcache.validate_timestamps=0 y validate_freq=60?
Con validate_timestamps=0, OPcache nunca verifica si el archivo PHP cambió — necesitas hacer reset manual tras el deploy. Con validate_timestamps=1 y revalidate_freq=60 verifica cada 60s. El modo 0 es más rápido pero exige un pipeline de deploy que invalide la caché.
¿Cómo invalidar OPcache tras un deploy sin reiniciar PHP-FPM?
Usa opcache_reset() vía un script PHP accedido por CLI o HTTP autenticado, o cachetool (cachetool opcache:reset --fcgi=/var/run/php/php8.3-fpm.sock). Reiniciar PHP-FPM funciona pero corta las conexiones en curso — el reset programático es más limpio en producción.
¿Cuánta memoria debo asignar a opcache.memory_consumption?
Empieza con 256 MB para aplicaciones medianas (Laravel, Symfony, WordPress con plugins). Monitorea vía opcache_get_status(): si memory.free_memory cae por debajo del 10% o wasted_memory sube mucho, auméntalo. Frameworks modernos como Laravel consumen fácilmente 128-200 MB de caché.
¿OPcache JIT (PHP 8+) vale la pena en aplicaciones web?
Para apps web tradicionales (Laravel, WordPress) la ganancia del JIT es modesta, 5-15% en benchmarks. El JIT brilla en cargas CPU-bound: procesamiento de imágenes, cálculos matemáticos, parseo complejo. Habilítalo con opcache.jit=tracing y opcache.jit_buffer_size=128M si tu app tiene componente computacional pesado.
¿Por qué opcache_get_status() retorna false incluso con OPcache habilitado?
Generalmente es opcache.restrict_api configurado para un path específico que no incluye el script llamador, o opcache.enable_cli=0 al ejecutar vía CLI. Verifica opcache.restrict_api en php --ri opcache y usa opcache.enable_cli=1 para pruebas vía línea de comandos.