Rendimiento de WordPress: la configuración de caché que realmente resiste el tráfico real

¿Te fue útil?

Los picos de tráfico no matan a WordPress. El comportamiento inesperado de la caché sí. El sitio parece estar bien a 30 peticiones por segundo… hasta que la caché de la página de inicio expira,
se dispara una purga, los usuarios autenticados evitan la caché y de repente PHP y MySQL están haciendo CrossFit en contra de tu voluntad.

Esta es la configuración práctica y de nivel producción que aguanta cuando marketing “manda” una campaña, un rastreador se entusiasma o tu CEO
refresca la página principal 400 veces para “probarla”. Construiremos una capa de caché por niveles, definiremos qué no debe cachearse y haremos que la historia de las purgas sea aburrida—porque
lo aburrido es como ocurre la disponibilidad.

Qué realmente resiste el tráfico real (y qué no)

Las conversaciones sobre rendimiento de WordPress suelen empezar por los plugins. Al tráfico real no le importa tu lista de plugins. Le importa si tu
arquitectura puede servir el 95% de las peticiones que deberían ser idénticas sin ejecutar PHP en absoluto.

Una configuración que sobreviva tiene tres propiedades:

  • Capas: caché CDN/edge, caché de página completa en origen, caché de objetos y solo entonces cómputo.
  • Reglas de bypass correctas: sesiones autenticadas, carritos, previews y el área de administración no deben cachearse de la misma manera que las páginas públicas.
  • Invalidación controlada: purgas estrechas y predecibles, y evitar estampidas cuando las cachés expiran.

Si tu “caché” todavía significa “golpea PHP, y tal vez usa un plugin para almacenar HTML en disco,” estás optimizando lo equivocado. I/O de disco y workers de PHP
no son donde quieres que caiga tu tráfico pico.

El objetivo: para tráfico anónimo, devolver una respuesta cacheada desde el CDN o Nginx en unos pocos milisegundos, con PHP dormido y MySQL felizmente ignorante.
Para flujos autenticados y transaccionales, hacer la ruta dinámica eficiente y estable. No puedes cachear una checkout rota, pero sí puedes
evitar que la página principal la encienda en llamas.

Datos y contexto histórico (breve, útil, ligeramente alarmante)

  1. WordPress empezó (2003) en una era en que “cachear” a menudo significaba “activar gzip y rezar.” Los patrones de tráfico modernos son más duros: bots, scrapers y vistas previas de enlaces actúan como pruebas de carga permanentes.
  2. Memcached precede a Redis y fue el caché de objetos preferido para el escalado temprano de WordPress; Redis ganó popularidad después porque la persistencia, las estructuras de datos y las herramientas operativas mejoraron la experiencia del día 2.
  3. Varnish popularizó el caché HTTP frente a apps dinámicas; Nginx luego absorbió gran parte de ese rol para muchos equipos con FastCGI cache y operaciones más simples.
  4. La “explosión de cookies” es un impuesto moderno de WordPress: analytics, test A/B, widgets de chat, banners de consentimiento—cada cookie puede destruir silenciosamente la tasa de aciertos si se varía en ella.
  5. HTTP/2 no resolvió los cuellos de botella del backend: mejoró el manejo de conexiones y multiplexación, pero si los workers de PHP-FPM están saturados, el navegador sigue esperando con paciencia.
  6. La mayoría de sitios WordPress son lecturas hasta que dejan de serlo: una campaña de marketing puede convertir un sitio “principalmente de lectura” en “todos golpean /wp-admin/admin-ajax.php” en minutos.
  7. La caché de consultas de MySQL desapareció (eliminada en MySQL 8). Si aún confías en ese concepto, vives en un museo con Wi‑Fi muy rápido.
  8. OPcache no es opcional para el rendimiento de PHP; sin él, PHP recompila scripts bajo carga y pagas CPU por el privilegio de ser lento.

La arquitectura objetivo: caché por capas que se puede razonar

Esta es la pila que realmente se comporta bajo presión:

  • CDN (edge): cachea páginas públicas y recursos; aplica claves de caché sensatas; protege el origen de picos.
  • Caché de página completa en origen Nginx: FastCGI cache para GET/HEAD anónimos; microcaching para endpoints concretos si es necesario.
  • PHP-FPM + OPcache: conteo de workers afinado; memoria estable; nada de “ruleta de max_children”.
  • Caché de objetos Redis: cachea búsquedas de objetos costosas de WP; evita golpes repetidos a la BD por options/transients.
  • MySQL: afinado para concurrencia; visibilidad de queries lentas; índices que reflejen la realidad.
  • Observabilidad: métricas de hit de caché, tiempos upstream y clasificación de peticiones (cacheada vs bypass vs dinámica).

Notarás lo que falta: “un plugin que promete 10x de velocidad.” Los plugins ayudan con integración y hooks de purga, pero el mecanismo central de supervivencia
está en la capa HTTP. Ahí es donde evitas que la carga entre en tu runtime en primer lugar.

Una idea para pegar en cada laptop de on-call, parafraseada y atribuida a Werner Vogels (reliability/arquitectura): Todo falla eventualmente; diseña para que los modos de falla estén contenidos y sean predecibles.

Broma #1: Invalidar caché es difícil. El otro problema difícil es explicarle a finanzas por qué “más CPU” no arregló “demasiadas peticiones.”

Capa 1: caché CDN que no rompe tu sitio

Si tienes un CDN y tu origen aún ve la mayor parte del tráfico anónimo, tu CDN es básicamente un terminador TLS muy caro.
El primer trabajo es hacer que el CDN cachee con confianza lo que es seguro.

Qué debe cachear el CDN

  • Recursos estáticos: imágenes, CSS, JS, fuentes. TTL largo, nombres de archivo inmutables si es posible.
  • Páginas HTML públicas para usuarios anónimos: página principal, entradas, categorías, etiquetas, páginas de marketing.
  • Algunas respuestas de API si las controlas y son públicas (raro en WordPress a menos que frontalices un endpoint de solo lectura).

Qué no debe cachear el CDN (o debe variar con cuidado)

  • Cualquier cosa que cambie por usuario: páginas autenticadas, admin, cuenta, checkout, carrito.
  • Respuestas que establecen cookies o dependen de cookies a menos que tu clave de caché sea disciplinada.
  • Páginas de preview, borradores, entradas protegidas por contraseña.

Consejo práctico: trata a Set-Cookie como símbolo de desecho tóxico para HTML público. Si el origen está estableciendo cookies en vistas anónimas
porque un plugin “lo necesita”, la tasa de aciertos de tu CDN morirá en silencio.

Disciplina de clave de caché: de ello depende tu tasa de aciertos

Por defecto, muchos CDNs pueden variar la caché en una larga lista: query strings, encabezados y a veces cookies. Eso es un arma para asesinar la tasa de aciertos.
Para WordPress, típicamente quieres:

  • Variar por ruta URL y un conjunto controlado de parámetros de consulta (a menudo ninguno para HTML).
  • Ignorar la mayoría de cookies para tráfico anónimo, pero evitar la caché cuando existan cookies específicas de WordPress.
  • Honrar Cache-Control del origen solo si confías en que tu origen está correcto. La mayoría no empieza correcto.

Si tu CDN soporta “servir contenido obsoleto ante error” y “servir obsoleto mientras revalida”, actívalos para contenido público. Es la diferencia entre
“el origen está enfermo pero los usuarios están bien” y “el origen está enfermo y todo el mundo lo sabe.”

Reglas de clave de caché: cookies, encabezados y por qué tu tasa de aciertos miente

WordPress establece y lee algunas cookies que importan:

  • wordpress_logged_in_*: el usuario está autenticado. Evitar caché de página completa y caché HTML del CDN.
  • wp-postpass_*: contenido protegido por contraseña. No cachear públicamente.
  • woocommerce_cart_hash, woocommerce_items_in_cart: estado transaccional. Trátalas como señales de bypass.
  • comment_author_*: puede afectar la renderización de la página para formularios de comentarios. Usualmente es mejor evitar caché o variar con cautela.

Ahora el problema más sutil: cookies de terceros. Muchos plugins establecen cookies para “grupo de test A/B”, “fuente de referencia”, “consentimiento”, “sesión de chat” y demás. Si tu capa de caché varía por cookie o si el origen envía HTML diferente basado en estas cookies, fragmentas tu caché en piezas pequeñas.

Regla de decisión: si una cookie no cambia de forma material el HTML para usuarios anónimos, no debe formar parte de la clave de caché. Si sí cambia el HTML, necesitas decidir si esa personalización vale el coste de rendimiento. En la mayoría de entornos corporativos, la respuesta es “no, no en las páginas de aterrizaje críticas.”

Capa 2: caché de página completa en origen (Nginx FastCGI) bien hecha

El caché de página completa en origen es el caballo de batalla. Cuando el CDN falla (edge frío, purga, comportamiento geográfico), tu origen debería seguir sirviendo HTML cacheado
sin ejecutar WordPress. Nginx FastCGI cache hace esto de forma fiable si lo configuras con reglas de adulto.

Por qué FastCGI cache supera a “plugins de caché de archivos HTML” en producción

  • Concurrencia: Nginx puede servir respuestas cacheadas con sobrecarga mínima, incluso bajo alta carga.
  • Aislamiento: la caché se sitúa delante de PHP, así que la agotación de workers PHP no derriba inmediatamente las páginas públicas.
  • Control: puedes definir claves de caché, reglas de bypass y TTLs en un solo lugar, y puedes observarlo.

Patrón base de Nginx FastCGI cache (conceptual)

Quieres una clave de caché que ignore la basura, un bypass para cookies de login/carrito/admin y una protección contra estampidas.
Usa fastcgi_cache_lock para que una petición caliente la caché mientras las demás esperan brevemente en lugar de lanzar un dogpile a PHP.

Guía práctica de TTL:

  • Entradas/páginas públicas: 5–30 minutos en origen es común si tienes hooks de purga; más tiempo si no los tienes.
  • Página principal: a menudo más corta si cambia con frecuencia, pero aún cacheada.
  • Páginas de búsqueda: complicado; a menudo cachear brevemente (30–120 segundos) o omitirse si están personalizadas.

Microcaching (1–5 segundos) es una herramienta legítima para endpoints que no puedes cachear completamente pero que reciben golpes masivos (algunos endpoints AJAX). No empieces por ahí.
Úsalo cuando tengas evidencia.

Capa 3: caché de objetos (Redis) sin convertirlo en un basurero

El caché de objetos ayuda más cuando tus páginas no están completamente cacheadas: dashboards autenticados, flujos de WooCommerce y cualquier sitio con muchos bloques dinámicos.
Reduce consultas repetidas a la base de datos para options, transients y búsquedas repetidas dentro de una misma petición y entre peticiones.

Pero el caché de objetos Redis tiene un modo de fallo: puede convertirse en un cajón compartido de claves sin límite, TTLs impredecibles y plugins “útiles” que almacenan fragmentos renderizados completos. Cuando Redis empieza a expulsar claves calientes o a swapear, no solo irá lento—será creativamente lento.

Reglas que mantienen a Redis útil

  • Limitar memoria: configura maxmemory y una política de expulsión sensata (a menudo allkeys-lfu para cargas mixtas).
  • Mantén Redis local cuando sea posible: la latencia de red suma. Si debe ser remoto, mantenlo cerca y monitoriza p99.
  • Usa un prefijo claro: evita colisiones de claves y haz posible purgar por prefijo si hace falta.
  • Vigila fragmentación y expulsiones: las expulsiones no son “normales.” Son síntoma de “estamos adivinando”.

Cuando el caché de objetos no ayuda

  • Si ya sirves la mayor parte del tráfico desde CDN + caché de página completa, el caché de objetos no cambia la línea principal.
  • Si tu cuello de botella es CPU de PHP por lógica pesada en plantillas, Redis no te salvará mucho.
  • Si tu base de datos es lenta por índices faltantes, el caching puede enmascarar el problema hasta que ya no pueda más.

Capa 4: PHP-FPM y OPcache (porque las fallas de caché son reales)

Puedes construir una gran caché y aún así fundirte porque la ruta no cacheada es inestable. Usuarios autenticados, pantallas de admin y calentamientos de purga golpearán PHP.
Si PHP-FPM está afinado como un proyecto hobby, se comportará como tal.

PHP-FPM: afina por memoria primero, luego por concurrencia

El error clásico es establecer pm.max_children en base al número de CPU. WordPress consume mucha memoria. Debes fijar max children en función de:
(RAM disponible – margen de seguridad) / RSS promedio de proceso PHP.

También necesitas observar:

  • Eventos de max children reached: significa que las peticiones están en cola.
  • entradas en slow log: significa que scripts específicos están tardando demasiado.
  • distribución de duración de peticiones: p95 y p99 importan más que la media.

OPcache: la mejora más barata que puedes comprar

Con OPcache activado y dimensionado correctamente, PHP evita recompilar scripts y mantiene código caliente en memoria.
Bajo carga, un OPcache mal dimensionado se manifiesta como CPU alta, reinicios frecuentes y “¿por qué esto está más lento después del deploy?”

Capa 5: ajuste de MySQL para cargas de trabajo de WordPress

WordPress no es exótico. Es una app web clásica: muchas lecturas, algunas escrituras y unas pocas queries que se vuelven monstruosas cuando tu dataset crece.
La mayor parte del dolor en la base de datos de WordPress proviene de:

  • Índices faltantes para tablas creadas por plugins o consultas por meta.
  • Inflación de autoloaded options en wp_options.
  • Consultas lentas en admin que nadie prueba con el volumen de datos de producción.
  • Tormentas de conexiones cuando las cachés expiran y los workers PHP se disparan.

La base de datos no debe ser tu caché de páginas. Debe ser una fuente durable de verdad. Si tu página principal necesita 200 queries, el caching lo oculta; no lo arregla. Pero con las capas de caché correctas, la carga en BD se vuelve predecible y puedes afinar e indexar basándote en los outliers reales.

Invalidación y purga de caché: predecible vence a ingenioso

“Purgar todo al publicar” es el equivalente en WordPress a tirar de la alarma de incendios para probarla. Claro, la alarma funciona. Ahora todos están afuera y enfadados.

La invalidación debe ser:

  • Estrecha: purgar la URL que cambió y el pequeño conjunto de páginas que la referencian (página de categoría, página principal, feeds).
  • Limitada por tasa: especialmente para actualizaciones masivas, importaciones y flujos editoriales.
  • Observable: debes poder ver el volumen de purgas y correlacionarlo con caídas en la tasa de aciertos y la carga del origen.

La protección contra estampidas importa. Si purgas una URL popular y 5.000 usuarios la solicitan a la vez, quieres una petición que reconstruya y el resto espere o reciba contenido obsoleto brevemente.

Broma #2: La forma más fácil de aumentar la tasa de aciertos es publicar menos. Los editores no han abrazado esta idea revolucionaria.

Guion de diagnóstico rápido: encuentra el cuello de botella en minutos

Cuando el sitio está lento, no tienes tiempo para filosofía. Necesitas una clasificación rápida: ¿estamos sin caché, saturados en PHP o bloqueados en la base de datos? Aquí está el orden que encuentra respuestas rápido.

Primero: confirma dónde se pasa el tiempo (edge vs origen vs upstream)

  1. Comprueba los encabezados de estado del CDN/edge (HIT/MISS/BYPASS). Si estás mayormente en MISS, arregla las reglas de cacheo antes de tocar PHP.
  2. Comprueba los encabezados de respuesta del origen para el estado del FastCGI cache (HIT/MISS/BYPASS/EXPIRED).
  3. Mide TTFB desde fuera y compáralo con los tiempos del origen. Si el edge es rápido pero el origen es lento, tienes un problema en origen pero los usuarios pueden estar bien.

Segundo: revisa señales de saturación

  1. PHP-FPM: max children reached, listen queue, entradas en slowlog.
  2. MySQL: threads en ejecución, queries lentas, esperas por locks.
  3. CPU y memoria: swapping, steal time (VM), iowait (almacenamiento).

Tercero: identifica la clase de petición que causa el problema

  1. ¿Es /wp-admin/admin-ajax.php?
  2. ¿Es la búsqueda?
  3. ¿Es un crawler ignorando robots?
  4. ¿Es una tormenta de purgas?

El camino más rápido hacia la estabilidad a menudo no es “optimizar WordPress.” Es “deja de enviar tráfico dinámico a WordPress.”
Eso significa cachear, limitar tasa y disciplina de bypass.

Tareas prácticas (comandos + salidas + decisiones)

Estas son las tareas que realmente ejecuto durante respuesta a incidentes y afinamiento de rendimiento. Cada una incluye: comando, qué significa la salida y la
decisión que tomas.

Tarea 1: Medir TTFB y confirmar encabezados de caché desde el edge

cr0x@server:~$ curl -s -D- -o /dev/null https://example.com/ | egrep -i '^(HTTP/|age:|cache-control:|cf-cache-status:|x-cache:|via:|server-timing:)'
HTTP/2 200
cache-control: public, max-age=300
age: 142
cf-cache-status: HIT
via: 1.1 varnish

Significado: cf-cache-status: HIT y un age distinto de cero sugieren que el CDN está sirviendo HTML cacheado. Bien.

Decisión: Si ves MISS para tus páginas principales durante tráfico normal, arregla reglas de caché del CDN y lógica de bypass de cookies antes de tocar el origen.

Tarea 2: Comparar edge vs origen directamente (evitar CDN)

cr0x@server:~$ curl -s -D- -o /dev/null --resolve example.com:443:203.0.113.10 https://example.com/ | egrep -i '^(HTTP/|x-fastcgi-cache:|x-cache:|server:|cache-control:)'
HTTP/2 200
server: nginx
cache-control: public, max-age=300
x-fastcgi-cache: HIT

Significado: Golpeaste la IP de origen; x-fastcgi-cache: HIT significa que Nginx está sirviendo HTML cacheado sin PHP.

Decisión: Si el origen es MISS mientras el CDN es MISS, espera un colapso: estás ejecutando WordPress para tráfico anónimo. Arregla el caché de página completa en origen inmediatamente.

Tarea 3: Comprobar ratio de aciertos de caché de Nginx desde logs (rápido y sucio)

cr0x@server:~$ sudo awk '{print $NF}' /var/log/nginx/access.log | sort | uniq -c
  81234 HIT
  10421 MISS
   3211 BYPASS
    442 EXPIRED

Significado: Esto asume que tu formato de log termina con el estado de caché. Mucho HIT es saludable; muchos BYPASS indica reglas de cookies o tráfico autenticado.

Decisión: Si BYPASS es alto para páginas que deberían ser públicas, encuentra qué cookies están causando bypass y deja de establecerlas para usuarios anónimos.

Tarea 4: Identificar las URLs principales que causan carga en el origen

cr0x@server:~$ sudo awk '{print $7}' /var/log/nginx/access.log | sort | uniq -c | sort -nr | head
  15432 /wp-admin/admin-ajax.php
   9821 /
   6110 /wp-json/wp/v2/posts
   5880 /product/widget-1/
   4122 /?s=widget

Significado: Muestra los endpoints más concurridos. Admin AJAX y búsqueda son sospechosos habituales.

Decisión: Si /wp-admin/admin-ajax.php domina, investiga qué lo llama (features front-end, heartbeat, plugins), luego limita tasa o cachea/microcache donde sea seguro.

Tarea 5: Comprobar saturación de PHP-FPM (página de estado)

cr0x@server:~$ curl -s http://127.0.0.1/php-fpm-status | egrep -i 'pool|process manager|active processes|idle processes|max active processes|listen queue'
pool:                 www
process manager:      dynamic
active processes:     48
idle processes:       2
max active processes: 50
listen queue:         37

Significado: Estás en el techo; la cola se está formando. Las peticiones esperan por workers.

Decisión: Si la memoria lo permite, aumenta pm.max_children. Si la memoria no lo permite, reduce la carga dinámica mediante cacheo/reglas de bypass y arregla endpoints caros.

Tarea 6: Confirmar eventos “max children reached”

cr0x@server:~$ sudo journalctl -u php8.2-fpm --since "30 min ago" | egrep -i 'max_children|max children'
Feb 04 10:12:09 web1 php-fpm8.2[1203]: [WARNING] [pool www] server reached pm.max_children setting (50), consider raising it

Significado: Evidencia clara de saturación.

Decisión: Trata esto como una señal de planificación de capacidad. O aumentas workers (si la RAM lo permite) o reduces el tráfico no cacheado que llega a PHP.

Tarea 7: Estimar memoria por worker PHP para fijar max_children de forma segura

cr0x@server:~$ ps -ylC php-fpm8.2 --sort:rss | awk 'NR==1{print} NR>1{rss+=$8; n++} END{printf "avg_rss_kb=%d\n", rss/n}'
S   UID     PID    PPID  C PRI  NI   RSS    SZ WCHAN  TTY          TIME CMD
avg_rss_kb=142000

Significado: Memoria residente promedio por worker ~142MB. Eso es típico para WordPress con plugins.

Decisión: Si tienes, digamos, 8GB libres para PHP, no puedes establecer 200 workers. Harás swapping y el rendimiento morirá lenta y públicamente.

Tarea 8: Comprobar estado de OPcache y presión de memoria

cr0x@server:~$ php -i | egrep -i 'opcache.enable|opcache.memory_consumption|opcache.interned_strings_buffer|opcache.max_accelerated_files'
opcache.enable => On => On
opcache.memory_consumption => 256
opcache.interned_strings_buffer => 16
opcache.max_accelerated_files => 20000

Significado: OPcache está activado y dimensionado. La memoria aún puede ser insuficiente dependiendo del tamaño del codebase.

Decisión: Si los deploys causan picos de CPU y reinicios de caché, aumenta la memoria de OPcache y valida que no estés reiniciando PHP-FPM con frecuencia.

Tarea 9: Comprobar salud de Redis para caché de objetos

cr0x@server:~$ redis-cli INFO memory | egrep -i 'used_memory_human|maxmemory_human|mem_fragmentation_ratio'
used_memory_human:1.42G
maxmemory_human:2.00G
mem_fragmentation_ratio:1.57

Significado: Redis está usando 1.42G de 2G, la fragmentación es moderada/alta. La fragmentación puede subir bajo churn.

Decisión: Si la fragmentación sube y la latencia aumenta, considera ajustar la política de expulsión, reducir el churn de claves (plugins malos) o provisionar headroom.

Tarea 10: Comprobar expulsiones en Redis (un asesino silencioso de rendimiento)

cr0x@server:~$ redis-cli INFO stats | egrep -i 'evicted_keys|expired_keys|keyspace_hits|keyspace_misses'
keyspace_hits:98234123
keyspace_misses:7712231
expired_keys:412332
evicted_keys:118443

Significado: Las expulsiones significan que Redis está bajo presión de memoria y está descartando claves que querías cachear.

Decisión: Si las expulsiones ocurren durante el pico, aumenta la memoria de Redis, cambia lo que almacenas o fija TTLs de forma más sensata. No finjas que las expulsiones están bien.

Tarea 11: Comprobar threads y contención en MySQL rápidamente

cr0x@server:~$ mysql -e "SHOW GLOBAL STATUS LIKE 'Threads_running'; SHOW GLOBAL STATUS LIKE 'Threads_connected';"
+-----------------+-------+
| Variable_name   | Value |
+-----------------+-------+
| Threads_running | 64    |
+-----------------+-------+
+-------------------+-------+
| Variable_name     | Value |
+-------------------+-------+
| Threads_connected | 210   |
+-------------------+-------+

Significado: Muchos threads en ejecución sugieren que la BD está ocupada; threads conectados sugiere presión de conexiones.

Decisión: Si los threads en ejecución suben cuando las cachés expiran, tienes un problema de estampida. Añade locks de caché, servir obsoleto y reduce el tráfico de bypass.

Tarea 12: Encontrar queries lentas (los principales ofensores)

cr0x@server:~$ sudo mysqldumpslow -s t -t 10 /var/log/mysql/mysql-slow.log
Count: 18  Time=2.31s (41s)  Lock=0.00s (0s)  Rows=1.0 (18), root[root]@localhost
  SELECT option_value FROM wp_options WHERE option_name = 'autoload_big_blob' LIMIT 1;
Count: 9  Time=1.97s (17s)  Lock=0.12s (1s)  Rows=1200.0 (10800), app[app]@10.0.0.12
  SELECT * FROM wp_postmeta WHERE meta_key = '...' AND meta_value LIKE '...%';

Significado: Las búsquedas en options y consultas meta sin índices te están matando. La segunda query grita “plugin haciendo una búsqueda por meta.”

Decisión: Arregla la inflación de autoload, añade índices específicos donde sea seguro y considera rediseñar consultas (o desactivar la funcionalidad del plugin). El caching por sí solo no hará rápido un LIKE sin índice.

Tarea 13: Comprobar tamaño de opciones autoloaded (mina común en WordPress)

cr0x@server:~$ mysql -e "SELECT ROUND(SUM(LENGTH(option_value))/1024/1024,2) AS autoload_mb FROM wp_options WHERE autoload='yes';"
+-------------+
| autoload_mb |
+-------------+
| 18.47       |
+-------------+

Significado: 18MB de options autoloaded significa que cada petición carga un montón de cosas en memoria. Esto crece silenciosamente con el tiempo.

Decisión: Audita qué options están autoloaded, desactiva autoload para blobs grandes y arregla el plugin/tema que lo genera.

Tarea 14: Confirmar cache-control y vary desde el origen

cr0x@server:~$ curl -s -D- -o /dev/null https://example.com/ | egrep -i 'cache-control:|pragma:|expires:|vary:|set-cookie:'
cache-control: public, max-age=300
vary: Accept-Encoding

Significado: No hay Set-Cookie en la página principal pública, y Vary no está explotando en encabezados irrelevantes.

Decisión: Si ves Set-Cookie en páginas públicas, encuentra el plugin que lo pone y páralo. Si no, estás pagando por caché y no obteniendo caché.

Tarea 15: Detectar picos de bots y actores malos rápidamente

cr0x@server:~$ sudo awk -F\" '{print $6}' /var/log/nginx/access.log | sort | uniq -c | sort -nr | head
  22110 Mozilla/5.0 (compatible; SomeBot/1.0; +http://bot.example)
  11842 Mozilla/5.0 (Linux; Android 10) AppleWebKit/537.36 Chrome/121.0 Mobile
   7441 Mozilla/5.0 (compatible; AnotherCrawler/2.1)

Significado: Un solo bot domina el tráfico. Eso puede estar bien si está cacheado; desastroso si evita la caché.

Decisión: Si los bots están golpeando endpoints dinámicos, añade limitación de tasa, endurece las reglas de robots y asegúrate de que las páginas públicas sean cacheables para que los bots sean baratos.

Tarea 16: Validar la distribución de tiempos de respuesta del upstream en Nginx

cr0x@server:~$ sudo awk '{print $(NF-1)}' /var/log/nginx/access.log | sed 's/upstream_response_time=//' | awk -F, '{print $1}' | sort -n | tail -n 5
0.842
1.003
1.217
2.884
5.991

Significado: Tus respuestas upstream más lentas son varios segundos. Probablemente son misses de caché que golpean PHP/MySQL.

Decisión: Investiga los endpoints lentos y alinea la política de caché: o los cacheas, o los haces más baratos (índices, arreglos de código), o los proteges (rate limits).

Errores comunes: síntomas → causa raíz → solución

1) La tasa de aciertos del CDN es baja aun cuando las páginas son “cacheables”

Síntomas: El CDN muestra muchos MISS/BYPASS; la carga en origen es alta durante picos.

Causa raíz: La clave de caché varía por cookies o query strings que no deberían variar; el origen envía Set-Cookie en HTML anónimo.

Solución: Elimina cookies irrelevantes de la clave de caché; evita solo en cookies de auth/carrito de WordPress; evita que plugins pongan cookies para usuarios anónimos; normaliza query strings.

2) “Funciona en staging” pero producción se derrite al expirar la caché

Síntomas: Cada pocos minutos, latencia alta; PHP y BD se disparan; luego “se recupera”.

Causa raíz: Estampida de caché: muchos clientes fallan a la vez; no hay bloqueo de caché; TTL corto en páginas calientes; tormentas de purga.

Solución: Habilita bloqueo de caché en origen, sirve obsoleto mientras se revalida en el edge, añade jitter a TTLs y deja de purgar todo.

3) Las páginas de WooCommerce muestran contenido incorrecto aleatoriamente

Síntomas: Contenido del carrito se filtra, “Hola Bob” aparece para Alice, precios varían inesperadamente.

Causa raíz: Caché de página completa aplicado a páginas personalizadas/transaccionales; la clave de caché ignora el estado de sesión.

Solución: Evita caché en cart/checkout/my-account, evita caché cuando existan cookies de WooCommerce y nunca cachees respuestas que establezcan cookies de sesión.

4) El admin es lento mientras el sitio público es rápido

Síntomas: Los editores se quejan; wp-admin hace timeouts; las páginas públicas están bien.

Causa raíz: Admin evita la caché de página completa y golpea queries pesadas (postmeta searches, autoload bloat) y ejecución lenta de PHP.

Solución: Añade caché de objetos Redis, reduce options autoloaded, arregla queries lentas, afina PHP-FPM para concurrencia autenticada y considera separar el tráfico de admin si es necesario.

5) Después de habilitar Redis, el rendimiento empeora

Síntomas: Mayor latencia, timeouts, CPU de Redis alta, expulsiones.

Causa raíz: Redis infra-provisionado; churn de claves y expulsiones; latencia por Redis remoto; uso de Redis para blobs transitorios gigantes.

Solución: Aumenta memoria, elige una política de expulsión sensata, mantén Redis cerca de la app, audita plugins que almacenan valores grandes y monitoriza expulsiones y latencia.

6) “Solo añade más workers PHP” causa swapping y colapso

Síntomas: Promedio de carga sube; iowait se dispara; todo se ralentiza; logs del kernel muestran presión de memoria.

Causa raíz: Conteo de workers fijado más allá de la capacidad de RAM; RSS por worker subestimado; OPcache o fugas de PHP; sin margen.

Solución: Mide RSS, fija max_children según memoria, añade margen y reduce tráfico dinámico vía caché. Escalar mal es peor que no escalar.

7) Ejecutar purgas causa breves caídas

Síntomas: Justo después de deploy/publicación, el origen se dispara; CDN pasa a MISS; latencia visible por el usuario se eleva.

Causa raíz: Purgas amplias (todo); sin warmup; sin bloqueo de caché; TTL demasiado corto.

Solución: Purga solo URLs cambiadas, limita tasa de purga, usa bloqueo de caché, precalienta páginas críticas y permite obsoleto en el edge.

Tres micro-historias corporativas desde las trincheras

Micro-historia 1: El incidente causado por una suposición errónea

Una empresa mediana usaba WordPress como puerta de marketing para un producto que había crecido rápido. Tenían CDN, una base de datos gestionada y una
plataforma de contenedores. Parecía moderno. El rendimiento estaba “bien” hasta que dejó de estarlo.

La suposición errónea: “Si ponemos Cache-Control: public, el CDN cacheará HTML y listo.” En realidad, un plugin de consentimiento ponía una
cookie en la primera visita de cada usuario, y su configuración de CDN variaba la caché por todas las cookies por defecto. Así que cada usuario obtuvo su propia
entrada privada de caché. La tasa de aciertos se hundió sin que nadie lo notara, porque el panel mostraba aún “peticiones servidas” y parecía impresionante.

Luego lanzaron una campaña. La página principal volvió a ser un endpoint PHP. PHP-FPM se saturó, las conexiones a MySQL se dispararon y los health checks
empezaron a fallar. El CDN no los protegió porque no estaba cacheando lo que pensaban que cacheaba.

La solución fue aburrida y efectiva: evitar caché solo en cookies específicas de WordPress, ignorar la cookie de consentimiento en la clave de caché y dejar de
poner cookies de marketing en respuestas HTML cuando no eran necesarias. También añadieron un FastCGI cache en origen como respaldo.

La lección: en el trabajo de rendimiento, el enemigo no es la complejidad. Es el comportamiento implícito. Si no puedes explicar tu clave de caché en una frase,
no tienes una caché—tienes un rumor.

Micro-historia 2: La optimización que salió mal

Otro equipo quería más “frescura” para los editores. Reducieron los TTLs de caché en todo a 30 segundos y añadieron purga agresiva al publicar. El sitio público se sentía
más ágil para actualizaciones de contenido. Los editores estaban contentos. Durante una semana.

El problema se mostró durante tráfico normal de semana. Cada 30 segundos, páginas populares expiraban en momentos similares. El CDN y la caché de origen
tuvieron misses sincronizados. De repente, miles de peticiones por minuto reconstruían páginas idénticas ejecutando PHP y golpeando MySQL. Fue una estampida
autoinfligida.

Intentaron “arreglarlo” aumentando workers de PHP-FPM. Eso empujó los servidores a presión de memoria y swapping intermitente. La latencia empeoró y fue menos predecible.
Los gráficos de monitorización parecían arte moderno: picos, color y no aptos para tomar decisiones.

La solución real: TTLs de origen más largos, bloqueo de caché, stale-while-revalidate en el edge y purgas dirigidas solo a URLs cambiadas. Añadieron jitter a TTLs de rutas
calientes para que las expiraciones no se alinearan. La frescura siguió siendo aceptable porque las purgas eran correctas, no porque los TTLs fueran mínimos.

La lección: bajar TTLs no es “más en tiempo real.” Es “más carga.” Si necesitas frescura, construye invalidación en la que puedas confiar.

Micro-historia 3: La práctica aburrida que salvó el día

Una gran empresa ejecutaba múltiples propiedades WordPress detrás de un edge compartido. El equipo no era llamativo. Tenían runbooks, restores probados y la costumbre
de medir antes de cambiar nada. Su “innovación” más grande fue anotar qué significaba cada cookie.

Durante un anuncio importante de producto, el tráfico subió y volvió a subir cuando un agregador de noticias lo recogió. Lo que debería haber sido un simulacro de incendio
se convirtió en una hora de tensión moderada mirando dashboards.

La razón: tenían una política de caché estricta para tráfico anónimo, con reglas explícitas de bypass para cookies de auth/carrito, y registraban el estado de caché en
cada petición. Cuando el edge empezó a ver más MISS por latencia de propagación regional, la FastCGI cache de origen ya estaba caliente y protegida por bloqueo.
La utilización de PHP subió ligeramente pero nunca alcanzó cola.

Un bot empezó a golpear consultas de búsqueda, que no estaban cacheadas. Sus reglas de rate limit lo atraparon, devolviendo 429s sin castigar a usuarios reales.
Los editores siguieron publicando sin purgar todo el sitio porque su integración de purga era estrecha y limitada por tasa.

La lección: prácticas aburridas—claves de caché documentadas, logging de estado de caché y rate limiting—no son burocracia. Son cómo evitas explicar una caída a gente que
no quiere aprender qué es una cookie.

Listas de verificación / plan paso a paso

Fase 1: Hacer barato el tráfico anónimo (1–2 días)

  1. Define “anónimo” con precisión: no wordpress_logged_in_*, no cookies de carrito de WooCommerce, no cookies de contraseña de post.
  2. Activa caché CDN para HTML público con una clave de caché controlada; bypass solo en cookies conocidas de auth/carrito.
  3. Asegura que el origen no establezca cookies en páginas públicas. Arregla el plugin que lo hace o configúralo para evitar cookies anónimas.
  4. Despliega caché de página completa en origen (Nginx FastCGI) con bloqueo de caché habilitado y reglas claras de bypass.
  5. Registra el estado de caché en CDN (si es posible) y en origen. Si no puedes medir la tasa de aciertos, estás adivinando.

Fase 2: Estabilizar la ruta dinámica (2–5 días)

  1. Activa OPcache y confirma que está dimensionado correctamente.
  2. Ajusta PHP-FPM max children basándote en RSS medido y RAM disponible.
  3. Despliega caché de objetos Redis si el tráfico autenticado y el admin son significativos. Fija maxmemory y monitorea expulsiones.
  4. Activa slow logs para PHP-FPM y MySQL. Identifica los principales ofensores antes de “optimizar”.
  5. Arregla la inflación de autoload en wp_options. Esto suele dar ganancias desproporcionadas.

Fase 3: Hacer segura la invalidación (continuo)

  1. Implementa purgas estrechas en publish/update: página cambiada + página principal + archivos relevantes, no “purgar todo.”
  2. Limita tasa de llamadas de purga y agrúpalas para operaciones masivas.
  3. Habilita servir obsoleto en el edge por ventanas breves durante revalidación o errores del origen.
  4. Precalienta selectivamente (homepage y páginas de aterrizaje top) después de deploys, no todo el sitemap.
  5. Valida continuamente las reglas de bypass cuando marketing añade scripts o plugins agregan cookies.

FAQ

1) ¿Realmente necesito tanto un caché CDN como un caché de página en origen?

Sí, si te importa sobrevivir a tráfico real. El CDN reduce latencia global y absorbe picos, pero los misses de caché ocurren (purgas, edges fríos,
geografía, encabezados). El caché en origen es tu respaldo para que los misses no se conviertan automáticamente en trabajo PHP.

2) ¿No basta con un plugin de caché de WordPress?

Los plugins ayudan con hooks de purga y algo de integración, pero el cacheo más robusto ocurre en la capa HTTP (CDN + Nginx). El caching en archivos por plugin puede servir en sitios pequeños, pero no es la arquitectura en la que quieres apostar tu respuesta a incidentes.

3) ¿Cómo sé si una cookie está matando mi tasa de aciertos?

Inspecciona encabezados de respuesta buscando Set-Cookie en páginas públicas y revisa las reglas de clave de caché del CDN. Luego correlaciona el estado de caché (HIT/MISS) con la presencia de ciertas cookies en las peticiones. Si las peticiones “anónimas” aún llevan docenas de cookies, espera fragmentación.

4) ¿Debo cachear resultados de búsqueda?

A veces. Si la búsqueda es pública y no personalizada, un cacheo de TTL corto (o microcaching) puede ayudar mucho. Si la búsqueda depende del estado del usuario o incluye precios por usuario, ten cuidado. Alternativamente, limita tasa de patrones abusivos y considera mejorar la implementación de búsqueda.

5) ¿Qué pasa con WooCommerce?

Cachea las páginas de producto para usuarios anónimos. Evita caché en carrito, checkout y páginas de cuenta, y para peticiones con cookies de carrito/sesión. También vigila endpoints AJAX y fragmentos; pueden generar carga sorprendente.

6) ¿Redis o Memcached para caché de objetos?

Ambos pueden funcionar. Redis suele ganar en flexibilidad operativa y herramientas en muchas organizaciones. La clave no es la marca; es dimensionar memoria, política de expulsión, latencia y disciplina de plugins.

7) ¿Cuánto deberían durar mis TTLs?

Lo bastante largos para ser útiles, lo bastante cortos para ser correctos. Si tienes purgas fiables, el TTL puede ser más largo. Si las purgas fallan, el TTL se convierte en tu red de seguridad. Para muchos sitios, 5–30 minutos en origen para páginas públicas es un punto de partida sensato, con caché en edge a menudo más largo.

8) ¿Debería activar “cache everything” en el CDN?

No de forma ciega. Puedes cachear HTML público de forma agresiva, pero debes implementar reglas de bypass explícitas para cookies de autenticación y transaccionales. “Cache everything” sin disciplina de cookies es cómo se obtienen incidentes de seguridad disfrazados de ganancias de rendimiento.

9) Mis páginas públicas son rápidas, pero las llamadas API son lentas. ¿Y ahora?

Identifica qué endpoints son lentos y si deberían ser cacheados o limitados por tasa. Para wp-json, decide qué es público y estable. Para admin AJAX, investiga los llamadores y reduce el ruido. Una página principal rápida no ayuda si el resto de la app es un generador de denegación de servicio.

Siguientes pasos que puedes desplegar esta semana

  • Instrumenta el estado de caché de extremo a extremo: estado de caché en edge, estado de FastCGI cache en origen, tiempo de respuesta upstream.
  • Arregla la clave de caché: evita solo en cookies de auth/carrito de WordPress; ignora el resto para HTML anónimo.
  • Despliega FastCGI cache en origen con bloqueo: evita estampidas que golpean PHP.
  • Haz las purgas estrechas: purga URLs cambiadas y un pequeño conjunto de dependencias; limita tasa para operaciones masivas.
  • Ajusta PHP-FPM según memoria: mide RSS; fija max_children de forma segura; confirma que no hay swapping.
  • Audita options autoloaded: reduce la inflación; elimina blobs generados por plugins del autoload.
  • Protege endpoints dinámicos calientes: limita tasa de patrones abusivos; microcache solo con evidencia.

El resultado que buscas no es “un sitio rápido en laboratorio.” Es un sitio que se mantiene rápido cuando llegan personas reales con navegadores reales, bots reales
y caos real. Caché por capas, reglas de bypass disciplinadas y invalidación aburrida te llevan ahí.

← Anterior
Proxmox PBS: Copias de seguridad exitosas pero restauraciones fallidas — La lista de verificación que las detecta
Siguiente →
Evitar que las aplicaciones se inicien automáticamente: el método de PowerShell que realmente funciona

Deja un comentario