Todo va bien hasta que deja de ir bien: el CEO intenta comprar una sudadera, el total del carrito está equivocado y tu “caché de alto rendimiento” sirve con orgullo la sesión de ayer al cliente de hoy. O tu formulario de generación de leads se envía, “funciona en mi máquina” y luego desaparece en un bucle de página de agradecimiento cacheada en producción.
El caché no es el villano. El caché mal definido sí lo es. El objetivo es velocidad sin errores funcionales: los carritos siguen siendo personales, las compras siguen siendo en tiempo real, los formularios siguen siendo únicos y las sesiones con usuario iniciado no se convierten en una transmisión pública.
Un modelo mental práctico: qué puedes cachear con seguridad
El caché en WordPress es una pila, no una característica. Puedes tener (y con frecuencia tienes) múltiples caches a la vez: caché del navegador, caché en el borde del CDN, caché de proxy inverso (Varnish o NGINX), caché de opcode de PHP, “page cache” de WordPress por plugin, caché de objetos (Redis/Memcached) y caches de base de datos. Cada uno resuelve un problema distinto y cada uno puede romper tu sitio a su manera.
Capas de caché y para qué sirven
- Caché del navegador (Cache-Control, ETag): ideal para assets estáticos (CSS/JS/imágenes). No para HTML que contiene personalización.
- Caché CDN: excelente para assets estáticos y, a veces, HTML anónimo si puedes omitirlo correctamente para sesiones y flujos de checkout.
- Proxy inverso / caché de página completa: ideal para tráfico anónimo. Peligroso para cualquier cosa dependiente de la sesión a menos que gestiones bien la lógica de cookies.
- Plugin de caché de páginas de WordPress: a menudo escribe HTML estático en disco. Fácil de desplegar, fácil de malconfigurar, puede entrar en conflicto con proxies/CDN.
- Caché de objetos (Redis): cachea resultados de consultas y objetos calculados. Generalmente seguro para usuarios con sesión iniciada y WooCommerce si el plugin se comporta bien.
- PHP OPcache: acelera la ejecución de PHP. Rara vez rompe funcionalidad; principalmente causa problemas en los despliegues cuando olvidas invalidarlo.
La regla que te mantiene fuera de problemas
Cachea solicitudes GET anónimas para páginas que no varían por usuario, ubicación, moneda, carrito o estado de autenticación. Todo lo demás debe omitirse (o cachearse con claves de variación explícitas que puedas defender en una revisión de incidente).
Eso es todo. Suena obvio. Falla en la práctica porque “anónimo” es resbaladizo: un usuario puede no estar técnicamente logueado pero aún tener un carrito, un selector de moneda o un nonce de formulario que debe ser único. Además, a WordPress le gustan las cookies. A WooCommerce le encantan las cookies. A tu suite de marketing le encantan las cookies. Las cookies son la forma en que tu caché aprende a dejar de ser caché.
Una verdad seca: si no puedes explicar por qué una respuesta está cacheada señalando cabeceras, claves y reglas de bypass, no tienes caché—tienes ruleta.
Broma corta #1: Si tu página de checkout es cacheable, felicidades—has inventado las compras comunales.
Qué nunca debería cachearse (HTML)
Estos son los no negociables para la mayoría de sitios WordPress:
- /wp-admin/ y /wp-login.php
- Cualquier página para usuarios con sesión iniciada (a menos que tengas un “cache privado” deliberado con claves por usuario, lo cual es raro y complejo)
- WooCommerce: /cart/, /checkout/, /my-account/, endpoints add-to-cart, endpoints de fragments
- Endpoints de formularios y páginas con nonces que expiran si dependes del comportamiento por defecto de nonces de WordPress
- Cualquier cosa con tokens CSRF, precios personalizados, recuentos de inventario que deben ser actuales o mensajes dependientes de sesión
Qué normalmente puedes cachear (HTML)
- Páginas públicas de marketing, entradas de blog, páginas de documentación
- Archivos de categoría/etiqueta para visitantes anónimos
- Páginas de listado de productos (PLP) para visitantes anónimos, si precios/moneda no varían
- Páginas de detalle de producto (PDP) para visitantes anónimos, con cuidado sobre mensajes de “en stock”
Define “varía” en serio
Si el HTML cambia según cualquiera de estos, tu caché debe omitir o variar la clave de caché:
- Cookies (carrito, sesión, consentimientos, tests A/B)
- Geo/país (impuestos, elegibilidad de envío, reglas de contenido)
- Moneda e idioma
- Tipo de dispositivo (rara vez vale la pena variar hoy en día; el diseño responsivo suele ganar)
- Cabeceras de autorización
Hechos interesantes y contexto histórico (porque el pasado aún te factura)
- El caché HTTP precede a WordPress por años. RFC 2616 (HTTP/1.1) formalizó la semántica de Cache-Control en 1999, y todavía discutimos sobre “must-revalidate” hoy.
- WordPress popularizó los “page cache plugins” temprano porque PHP era lento y el hosting compartido era barato. Escribir HTML estático en disco fue una táctica de supervivencia, no una elección estética.
- El carrito de WooCommerce está impulsado por cookies para invitados. Por eso “no logueado” no significa “seguro para cachear”.
- ESI (Edge Side Includes) fue un intento temprano de cachear páginas mixtas dinámicas. Funciona, pero tiene coste operativo; la mayoría de stacks WordPress lo evitan salvo que tengan un equipo de plataforma serio.
- Varnish se hizo famoso porque convirtió el caché HTTP en un proxy inverso de primera clase. Su lenguaje VCL es potente y también una gran forma de crear reglas de bypass difíciles de depurar.
- Los stampedes de caché se reconocieron como un problema de fiabilidad mucho antes de WordPress. Técnicas como request coalescing y “stale-while-revalidate” existen porque picos de tráfico castigan a los orígenes.
- Los navegadores se volvieron más estrictos respecto a cookies con el tiempo. Los valores por defecto de SameSite cambiaron el comportamiento, lo que puede alterar cómo las cookies de sesión interactúan con capas de caché.
- Muchos CDNs por defecto no cachean “todo” salvo que lo pidas explícitamente. Cuando la gente lo activa para HTML, a menudo olvida la lógica de bypass para carrito y checkout, y luego culpa a WooCommerce.
Cómo el caché rompe carritos y formularios: modos de fallo que realmente ocurren
1) HTML cacheado filtra contenido específico de la sesión
El clásico: el widget del carrito muestra los artículos de otro usuario, o el encabezado dice “Hola, Alex” a un extraño. Esto ocurre cuando las claves de caché de página completa no varían por las cookies correctas y cacheas una respuesta que incluía fragmentos personalizados.
Incluso si la página principal del carrito está excluida, el endpoint de fragment del carrito o el mini-carrito del encabezado pueden ser cacheados incorrectamente por un plugin, CDN o una regla de proxy demasiado entusiasta.
2) Totales de checkout desactualizados
Los impuestos y el envío pueden variar por dirección, país o incluso código postal. Si cacheas el HTML del checkout o las respuestas XHR usadas para calcular totales, puedes servir totales erróneos. Mejor caso: la pasarela de pago lo rechaza. Peor caso: cobras de menos y pasas el fin de semana aprendiendo finanzas a la fuerza.
3) Formularios fallan porque los nonces expiran o se comparten
Los nonces de WordPress son tokens basados en tiempo. Muchos plugins de formularios los incrustan en el HTML. Si cacheas el HTML demasiado tiempo el nonce expira, produciendo errores que parecen mensajes aleatorios de “Security check failed”. Si lo cacheas incorrectamente entre usuarios puedes crear comportamientos extraños tipo replay donde las presentaciones van al estado equivocado.
4) Páginas con sesión iniciada son cacheadas como anónimas (o viceversa)
Si tu capa de caché no respeta cookies como wordpress_logged_in_* o una cookie de autenticación personalizada, puedes cachear páginas de usuarios logueados públicamente. O puedes omitir la caché para todos porque estableciste una cookie amplia como “consent=true” y tu caché trata cualquier cookie como señal de bypass. Ambas son malas. Una es un incidente de seguridad; la otra es un incidente presupuestario.
5) “Optimizaciones” en conflicto: plugin de caché vs proxy vs CDN
Varias caches pueden trabajar juntas, pero solo si decides quién es la autoridad para HTML. Cuando un plugin de page cache establece cabeceras de una manera, NGINX las reemplaza y el CDN las ignora, tu depuración se convierte en una danza interpretativa.
6) Tormentas de purga y manadas atronadoras
Purgar toda la caché en cada actualización de producto es un comportamiento común por defecto. En tiendas con tráfico esto se convierte en un DOS autoinfligido: la caché se enfría, el origen se ve abrumado, la cola de PHP-FPM crece, luego timeouts, luego reintentos y más carga.
Hay una idea para tener en una nota adhesiva de Werner Vogels (CTO de Amazon): idea parafraseada: construye sistemas asumiendo que las cosas fallan y diseña para la recuperación sobre la perfección.
Guía de diagnóstico rápido: encuentra el cuello de botella y el bug con rapidez
Este es el playbook cuando alguien dice “el carrito está mal” o “los formularios no se envían” y necesitas señales rápido.
Primero: confirma si estás sirviendo HTML cacheado
- Revisa las cabeceras de respuesta para hits/misses de caché y Cache-Control.
- Compara anónimo vs con cookie de carrito (o cookie de usuario logueado) para ver si la caché varía correctamente.
- Revisa CDN vs origen: ¿el edge sirve una respuesta cacheada incluso cuando el origen dice no-store?
Segundo: aisla la capa que está cacheando
- Evita el CDN (host de origen o IP interna) y prueba de nuevo.
- Evita el proxy inverso (golpea PHP upstream directamente si es posible) y prueba de nuevo.
- Desactiva temporalmente el plugin de page cache (o pon el sitio en “modo desarrollo” si tu plugin lo soporta) y prueba de nuevo.
Tercero: valida endpoints específicos de WooCommerce y formularios
- Verifica que /cart/, /checkout/ y los endpoints de fragments de WooCommerce no estén cacheados.
- Verifica que las solicitudes POST nunca sean cacheadas (no deberían serlo, pero no confíes en los valores por defecto).
- Para formularios, verifica la frescura de los nonces y que la página que contiene el nonce no esté cacheada más allá de su TTL.
Cuarto: revisa la presión en el backend
- Si ves misses de caché, asegúrate de que el origen pueda manejar la tasa de misses: cola de PHP-FPM, latencia de base de datos, salud de Redis.
- Si ves hits de caché pero contenido incorrecto, céntrate en la variación de la clave de caché y reglas de bypass, no en “más hardware”.
Tareas prácticas: comandos, salida esperada y qué decidir
Estas tareas están diseñadas para un host Linux típico ejecutando NGINX + PHP-FPM, opcionalmente Varnish y Redis, frontado por un CDN. Ajusta rutas y nombres de servicios a tu stack. Cada tarea incluye (a) un comando, (b) qué significa la salida y (c) la decisión que tomas.
Tarea 1: Inspeccionar cabeceras de caché para una página pública
cr0x@server:~$ curl -sI https://store.example.com/ | egrep -i 'cache-control|age|expires|etag|x-cache|via|cf-cache-status|server'
server: nginx
cache-control: public, max-age=600
etag: "a1b2c3"
x-cache: HIT
age: 87
via: 1.1 varnish
Qué significa: Ves caché explícita (public, max-age=600) y un hit en el proxy inverso (x-cache: HIT) con Age en aumento. Bueno para la homepage si es segura para anónimos.
Decisión: Si es una página de marketing: mantenerla. Si la homepage incluye totales de carrito personalizados o “visto recientemente” para invitados: necesitas mover esos widgets al cliente o omitir el caché para usuarios con cookies relevantes.
Tarea 2: Inspeccionar cabeceras de caché para páginas de carrito y checkout
cr0x@server:~$ curl -sI https://store.example.com/cart/ | egrep -i 'cache-control|age|x-cache|cf-cache-status|set-cookie'
cache-control: no-store, no-cache, must-revalidate, max-age=0
x-cache: MISS
set-cookie: woocommerce_items_in_cart=1; path=/; secure; HttpOnly
Qué significa: El carrito está correctamente marcado como no cacheable y está en MISS (que es lo que quieres). La cookie indica estado de sesión/carrito.
Decisión: Si ves public o HIT aquí, trátalo como un bug de producción: añade reglas de bypass para estas rutas en cada capa de caché.
Tarea 3: Verificar variación cuando hay cookies presentes
cr0x@server:~$ curl -sI https://store.example.com/ -H 'Cookie: woocommerce_items_in_cart=1; wp_woocommerce_session_123=abc' | egrep -i 'cache-control|x-cache|age|vary'
cache-control: private, no-store, max-age=0
x-cache: BYPASS
vary: Accept-Encoding
Qué significa: Con cookies de carrito presentes, la caché se omite y la respuesta es private/no-store. Esa es una línea base sensata.
Decisión: Si aún dice x-cache: HIT, tu clave de caché está ignorando cookies o tu lógica de bypass no se dispara. Arregla esto antes de “optimizar” cualquier otra cosa.
Tarea 4: Confirmar comportamiento del CDN vs origen (conectar al origen directamente)
cr0x@server:~$ curl -sI https://origin.store.example.com/checkout/ | egrep -i 'cache-control|x-cache|age|server'
server: nginx
cache-control: no-store, no-cache, must-revalidate, max-age=0
x-cache: MISS
Qué significa: El origen no cachea checkout. Si el hostname público aún devuelve checkout cacheado, el culpable es el CDN.
Decisión: Añade reglas de bypass en CDN para rutas de checkout/cart/account y para cookies relevantes. No confíes en “respetar cabeceras del origen” a menos que lo hayas verificado en la práctica.
Tarea 5: Encontrar qué plugin de caché está activo (WordPress CLI)
cr0x@server:~$ cd /var/www/store
cr0x@server:~$ sudo -u www-data wp plugin list --status=active
+--------------------------+--------+-----------+---------+
| name | status | update | version |
+--------------------------+--------+-----------+---------+
| woocommerce | active | available | 8.6.1 |
| redis-cache | active | none | 2.5.3 |
| wp-super-cache | active | none | 1.9.4 |
+--------------------------+--------+-----------+---------+
Qué significa: Tienes un plugin de page cache (wp-super-cache) y un plugin de object cache (redis-cache) activo.
Decisión: Decide quién es responsable del caché de página completa: el plugin o el proxy inverso. Ejecutar ambos es posible pero usualmente crea incidentes “¿por qué está esto cacheado?”. Elige uno y configura el otro para que se mantenga en su carril.
Tarea 6: Comprobar si el object cache Redis se usa realmente
cr0x@server:~$ sudo -u www-data wp redis status
Status: Connected
Client: PhpRedis (v5.3.7)
Redis: 7.0.15
Drop-in: Valid
Qué significa: El object cache está funcionando. Esto reduce la carga en la base de datos y puede permitirte depender menos de caché HTML agresivo para flujos con usuario logueado.
Decisión: Mantén el object cache activado. Si el object cache está desconectado, arregla eso primero antes de endurecer TTLs de HTML; de lo contrario empujarás la carga de nuevo a MySQL.
Tarea 7: Verificar presencia de cookie de sesión de WooCommerce mientras se navega
cr0x@server:~$ curl -sI https://store.example.com/product/hoodie/ | egrep -i 'set-cookie|cache-control|x-cache'
cache-control: public, max-age=600
x-cache: HIT
Qué significa: La página de producto está cacheada y no establece cookies de carrito/sesión al verla. Eso es ideal.
Decisión: Si las páginas de producto están estableciendo wp_woocommerce_session_* para todos, tu tasa de hits de caché colapsará. Investiga plugins que inician sesiones en cada vista de página.
Tarea 8: Revisar la configuración de fastcgi_cache de NGINX y la lógica de bypass
cr0x@server:~$ sudo nginx -T 2>/dev/null | egrep -n 'fastcgi_cache|fastcgi_cache_bypass|fastcgi_no_cache|cache_key|set \$skip_cache' | head -n 40
123: fastcgi_cache_path /var/cache/nginx levels=1:2 keys_zone=WORDPRESS:100m inactive=60m max_size=5g;
181: set $skip_cache 0;
186: if ($request_method = POST) { set $skip_cache 1; }
190: if ($request_uri ~* "/(cart|checkout|my-account)/") { set $skip_cache 1; }
194: if ($http_cookie ~* "woocommerce_items_in_cart|wp_woocommerce_session_|wordpress_logged_in_") { set $skip_cache 1; }
221: fastcgi_cache_bypass $skip_cache;
222: fastcgi_no_cache $skip_cache;
223: fastcgi_cache WORDPRESS;
224: fastcgi_cache_key "$scheme$request_method$host$request_uri";
Qué significa: Este host usa NGINX fastcgi_cache. Hay bypass explícito para POST, rutas clave de WooCommerce y cookies clave.
Decisión: Asegúrate de que la clave de caché incluya la query string cuando sea relevante (ver más adelante). Asegúrate de omitir caché en las cookies correctas para tu sitio y evita omitir en cookies inocuas como analytics o consentimientos salvo que sea necesario.
Tarea 9: Probar si las query strings están colapsando entradas de caché
cr0x@server:~$ curl -sI "https://store.example.com/?utm_source=test1" | egrep -i 'x-cache|age'
x-cache: HIT
age: 140
Qué significa: La página cacheada no varió por query string, lo cual suele ser bueno para parámetros de seguimiento. Pero puede ser malo si tu sitio usa parámetros significativos (filtros, moneda, idioma).
Decisión: Elimina parámetros de marketing conocidos en el borde, pero varía la caché en parámetros funcionales (por ejemplo, ?currency=, ?lang=, filtros). No adivines: inventaría qué usa tu tema y plugins.
Tarea 10: Verificar comportamiento de Varnish y qué cookies causan pass
cr0x@server:~$ curl -sI https://store.example.com/ -H 'Cookie: wordpress_logged_in_abc=1' | egrep -i 'x-cache|via|set-cookie|cache-control'
via: 1.1 varnish
x-cache: MISS
cache-control: private, no-store, max-age=0
Qué significa: La cookie de usuario logueado desencadena un miss/bypass y una respuesta privada.
Decisión: Confirma que Varnish no está cacheando páginas autenticadas. Si ves HIT aquí, detente y arregla el VCL; podrías estar filtrando contenido privado.
Tarea 11: Comprobar presión de PHP-FPM durante misses de caché
cr0x@server:~$ sudo ss -lntp | egrep 'php-fpm|:9000'
LISTEN 0 4096 127.0.0.1:9000 0.0.0.0:* users:(("php-fpm8.2",pid=1211,fd=9))
Qué significa: PHP-FPM escucha localmente. Eso es normal.
Decisión: A continuación, revisa el estado del pool y la acumulación. Si la cola crece durante tráfico, tus reglas de bypass pueden ser demasiado amplias o tu TTL demasiado corto.
Tarea 12: Inspeccionar la página de estado del pool PHP-FPM (si está habilitada)
cr0x@server:~$ curl -s http://127.0.0.1/php-fpm-status | egrep -i 'listen queue|idle processes|active processes|max children reached'
listen queue: 0
idle processes: 12
active processes: 3
max children reached: 0
Qué significa: Sin cola, muchos workers ociosos. El origen está saludable.
Decisión: Si listen queue aumenta y max children reached incrementa, tienes un problema de capacidad en el origen. Aumenta la capacidad de FPM, reduce la tasa de misses o ambas cosas.
Tarea 13: Comprobar latencia MySQL rápidamente
cr0x@server:~$ mysqladmin -uroot -p ping; mysqladmin -uroot -p status
mysqld is alive
Uptime: 183204 Threads: 42 Questions: 23801984 Slow queries: 17 Opens: 231 Flush tables: 1 Open tables: 1024 Queries per second avg: 129.9
Qué significa: MySQL está arriba; existen consultas lentas pero no se observan picos obvios desde esta instantánea.
Decisión: Si las consultas lentas se disparan durante purgas, necesitas reducir el alcance de las purgas, añadir object cache o tunear índices/queries. No “arregles” cacheando checkout.
Tarea 14: Verificar que POST no esté cacheado por un intermediario
cr0x@server:~$ curl -s -o /dev/null -D - -X POST https://store.example.com/wp-admin/admin-ajax.php | egrep -i 'cache-control|x-cache|status|via'
HTTP/2 400
cache-control: no-store, no-cache, must-revalidate, max-age=0
via: 1.1 varnish
x-cache: MISS
Qué significa: La respuesta POST no está cacheada (y obtuviste un 400 por no enviar payload). Está bien para esta prueba.
Decisión: Si ves un HIT en un POST, tienes una mala configuración seria en proxy/CDN. Arregla inmediatamente; cachear POST rompe más que carritos—rompe la realidad.
Tarea 15: Revisar cron de WordPress y jobs en background (carga de purga)
cr0x@server:~$ sudo -u www-data wp cron event list --fields=hook,next_run,recurrence | head
+------------------------------+---------------------+------------+
| hook | next_run | recurrence |
+------------------------------+---------------------+------------+
| wp_version_check | 2025-12-27 03:12:00 | twice_daily|
| woocommerce_cleanup_sessions | 2025-12-27 02:45:00 | daily |
| wp_scheduled_delete | 2025-12-27 02:10:00 | daily |
+------------------------------+---------------------+------------+
Qué significa: La limpieza de sesiones de WooCommerce y otros cron corren regularmente. Algunos plugins de caché se enganchan en cron para purga/preload.
Decisión: Si ves trabajos de purge/preload demasiado frecuentes, limítalos. El preloading puede convertirse en un crawler autoinfligido que roba capacidad a usuarios reales.
Patrones de configuración que funcionan: plugin, servidor, CDN y cookies
Decide quién cachea HTML
Elige un caché HTML principal. Mi predeterminada con opinión para producción:
- Proxy inverso (NGINX fastcgi_cache o Varnish) para HTML anónimo.
- CDN para assets estáticos, opcionalmente HTML anónimo si tienes reglas de bypass disciplinadas.
- Caché de objetos (Redis) siempre, para anónimos y logueados.
- Plugin de page cache de WordPress solo si no controlas la capa de servidor. Si controlas el servidor, mantén los plugins al mínimo.
Cuantos más lugares puedan cachear accidentalmente una página de checkout, más probable es que, eventualmente, cacheen una página de checkout.
Bypass basado en cookies: el corazón de la seguridad en WooCommerce
WooCommerce establece cookies que señalan estado de carrito/sesión. Los nombres exactos varían, pero comúnmente verás:
woocommerce_items_in_cartwoocommerce_cart_hashwp_woocommerce_session_*wordpress_logged_in_*(para autenticación de WordPress)
En el proxy inverso o la capa de caché de NGINX, tu regla de bypass típicamente debería dispararse cuando cualquiera de esas esté presente. Eso mantiene el caché HTML limitado a navegación verdaderamente anónima.
Exclusiones por ruta: aún necesarias
Incluso con bypass por cookie, excluye explícitamente rutas sensibles. Porque los humanos probarán checkout sin carrito, sin cookies, y lo declararán “seguro”. Luego llega un comprador real con estado y el caché hace algo creativo.
Exclusiones comunes:
/cart/,/checkout/,/my-account//?wc-ajax=*y endpoints AJAX de WooCommerce/wp-admin/,/wp-login.php- Endpoints de formularios, especialmente si usas admin-ajax o rutas REST para envíos
Mantén “vary” intencional (y pequeño)
Si varías por demasiado, destruyes la tasa de aciertos de caché y luego te preguntas por qué el rendimiento no mejoró. Si varías por muy poco, filtras datos y rompes flujos.
Buenas razones para variar:
- Accept-Encoding (manejado automáticamente)
- País si tu sitio cambia impuestos/envío/contenido legal
- Moneda si los precios cambian
- Idioma si el contenido cambia
Malas razones para variar:
- Cookies de marketing aleatorias
- Estado de consentimientos (a menos que cambie sustancialmente el HTML)
- User agent (a menos que sirvas HTML completamente distinto)
Usa estrategias “stale” para prevenir stampedes
Si tu proxy inverso lo soporta, permite servir contenido stale para páginas anónimas mientras se revalida en segundo plano. El usuario obtiene una respuesta rápida; el origen no sufre un stampede cuando una página popular expira.
Pero no apliques contenido stale a endpoints transaccionales. Nadie quiere checkout stale.
Formularios: trata los nonces como leche, no como miel
Cuando un formulario incluye un nonce o token CSRF en el HTML, tienes tres opciones viables:
- No cachear la página del formulario (simple, seguro, puede costar rendimiento).
- Cachear la página pero renderizar el nonce dinámicamente vía AJAX o edge-side includes (más complejo, escalable).
- TTL corto para la página del formulario y aceptar que algunas pestañas abiertas por mucho tiempo fallarán (decisión de negocio; registra y monitoriza errores).
Broma corta #2: Un nonce cacheado es como una tarjeta de acceso de hotel fotocopiada—técnicamente una tarjeta, funcionalmente una queja.
CDN: cachea HTML solo cuando puedas omitir con precisión
Los CDNs son excelentes con assets estáticos. Para HTML, procede solo si:
- Puedes omitir según cookies y rutas.
- Puedes purgar selectivamente (por URL o tag) en lugar de “purge everything”.
- Puedes ver cabeceras de depuración (estado de caché, clave de caché o variación) durante incidentes.
Si tu regla de CDN es “cache todo” con TTL de 1 hora y un bypass a ojo, eventualmente cachearás algo que no querías. No es cinismo. Es tiempo.
El object cache no sustituye al page cache (y eso es bueno)
Redis object cache acelera la ejecución de WordPress sin servir el HTML de un usuario a otro. Suele ser la ganancia de rendimiento menos arriesgada que puedes desplegar en sitios con mucho WooCommerce, porque no ataja la lógica de request/response en la capa HTTP.
Úsalo para reducir la tentación de cachear HTML dinámico.
Tres mini-historias corporativas desde las trincheras del caché
Mini-historia 1: El incidente causado por una suposición errónea
La compañía: un minorista mediano con un stack WordPress “modernizado”. Nuevo proxy inverso de caché frente a PHP-FPM. Un demo de sprint mostró la homepage cargando en menos de 100ms. Todos aplaudieron. Alguien dijo “prácticamente resolvimos el rendimiento”.
La suposición errónea fue simple: “Si el usuario no está logueado, la respuesta es segura para cachear.” Es una frase que suena a ingeniería hasta que recuerdas que WooCommerce existe.
El lunes, soporte al cliente reportó compradores viendo “artículos ya en tu carrito” en la primera visita. Unos screenshares después, empeoró: el mini-carrito mostraba productos pertenecientes a otras sesiones. No datos de pago ni direcciones, pero sí una fuga de privacidad y un golpe a la credibilidad.
La depuración mostró que el proxy inverso ignoraba por completo las cookies de WooCommerce. Los invitados eran “anónimos”, pero su estado de carrito se rastreaba por wp_woocommerce_session_*. La clave de caché era solo host + uri. La primera persona en añadir un producto efectivamente “envenenó” el HTML cacheado para todos los que cayeran en esa página después.
La solución fue aburrida: omitir caché si hay cookies de sesión/carrito de WooCommerce y excluir explícitamente rutas de carrito/checkout. El seguimiento fue más importante: escribieron una página con la lista “qué hace que una respuesta sea específica de usuario” y la hicieron parte de cada revisión de cambios relacionada con caché.
Mini-historia 2: La optimización que salió mal
La compañía: un servicio por suscripción con mucho marketing de contenido y un checkout WooCommerce para add-ons. Querían mayor tasa de acierto en el CDN, así que decidieron “normalizar” cookies: eliminar la mayoría en el borde para hacer más solicitudes cacheables.
La idea sonaba inteligente. También estaba incompleta. Una de las cookies eliminadas era la cookie del selector de moneda establecida por un plugin. Otra era una cookie de selección de país usada para mostrar impuestos. No variaron la caché por país porque “la cabecera geo del CDN debería manejarlo”, excepto que no la conectaron en la clave de caché. Y el CDN, siendo una máquina, hizo exactamente lo que le dijeron.
Resultado: visitantes canadienses vieron precios en dólares estadounidenses en páginas de producto. Algunos llegaron al checkout y vieron que los totales cambiaban. Otros vieron mensajes de “impuesto incluido” que no aplicaban. Las conversiones bajaron y los tickets de soporte se dispararon. El incidente se clasificó como “regresión por cambio de rendimiento”, que es lenguaje corporativo para “rompimos dinero persiguiendo milisegundos”.
El plan de recuperación fue disciplinado: revertieron el stripping de cookies, luego lo reintrodujeron con listas blancas. Documentaron qué parámetros de query y cookies eran “funcionales” y debían permanecer en la variación de caché. También añadieron comprobaciones automatizadas: un script curl que compara cabeceras y un par de páginas representativas con y sin indicadores de moneda/país.
Eventualmente hicieron que el caching HTML del CDN funcionara solo para páginas de marketing anónimas, con bypass estricto en cualquier cosa relacionada con comercio. La tasa de acierto fue menor que el sueño original. Los ingresos dejaron de comportarse de manera extraña. Un trato justo.
Mini-historia 3: La práctica aburrida pero correcta que salvó el día
La compañía: SaaS B2B que usa WordPress para marketing y documentación, con formularios integrados a su CRM. Tenían un proxy inverso y un CDN, y estaban planeando una campaña que generaría un pico de tráfico.
En lugar de “subir el caché”, hicieron lo aburrido: listaron cada patrón de URL y endpoint que nunca debía cachearse, luego lo testearon en staging con cabeceras parecidas a producción. También crearon un estándar de “cabecera debug de caché”: el origen emitiría X-Cache-Bypass-Reason al omitir, y el proxy inverso emitiría X-Cache: HIT/MISS/BYPASS.
Durante la campaña el rendimiento fue bien—hasta que los formularios comenzaron a fallar para un subconjunto de usuarios. La reacción rápida habría sido culpar al plugin de formularios o al CRM. En su lugar, siguieron su propio playbook: revisar cabeceras en la página del formulario. Estaba siendo cacheada por 30 minutos en el CDN debido a un conjunto de reglas desajustado desplegado el día anterior.
Como tenían cabeceras de depuración consistentes, fue obvio en minutos qué capa era la culpable. Ajustaron la regla del CDN para omitir caché en páginas de formularios y cualquier respuesta que estableciera cookies específicas. Los errores pararon. La campaña continuó. Nadie tuvo que inventar una historia para liderazgo sobre “problemas intermitentes de terceros”.
No recibieron aplausos por el estándar de cabeceras. Consiguieron algo mejor: silencio en el canal de incidentes.
Errores comunes: síntoma → causa raíz → solución
1) El carrito muestra artículos de otro usuario
Síntoma: Mini-carrito o página del carrito muestra artículos inesperados; los usuarios reportan “carrito fantasma”.
Causa raíz: La caché de página completa no omite por cookies de sesión/carrito de WooCommerce, o se cachea el endpoint de fragments.
Solución: Omitir caché en woocommerce_items_in_cart, woocommerce_cart_hash, wp_woocommerce_session_*. Excluir /cart/, /checkout/, /?wc-ajax=*. Asegurar que el endpoint de fragments no esté cacheado en CDN o proxy.
2) Los totales de checkout cambian inesperadamente o son incorrectos
Síntoma: El envío/impuesto cambia entre pasos, o los totales no coinciden con la pasarela de pago.
Causa raíz: HTML de checkout cacheado o respuestas AJAX cacheadas usadas para calcular totales; falta variación por país/moneda.
Solución: Nunca cachear HTML de checkout. Omitir caché en flujos de selección de país/moneda. Para CDNs, desactivar caché en endpoints AJAX y checkout de WooCommerce por completo.
3) “Security check failed” en formularios
Síntoma: Errores de validación de nonce, fallos intermitentes en formularios, especialmente después de que una página ha estado abierta.
Causa raíz: Página cacheada contiene un nonce expirado; TTL demasiado largo; caché compartida entre usuarios.
Solución: Excluir páginas de formularios de la caché o generar nonces dinámicamente. Acortar TTL solo si aceptas fallos en pestañas abiertas por mucho tiempo; monitoriza las tasas de error.
4) Usuarios logueados ven páginas cacheadas como anónimas (o viceversa)
Síntoma: Barra de administración ausente, página de cuenta parece desconectada, o usuarios anónimos ven contenido privado.
Causa raíz: La caché no respeta cookies wordpress_logged_in_*; Authorization ignorada; clave de caché inconsistente.
Solución: Omitir por cookies de auth de WordPress y por la cabecera Authorization. Validar con curl usando cookies. Si necesitas caché para páginas logueadas, implementa variación por usuario explícitamente (raro; probar intensamente).
5) La tasa de hits colapsa tras añadir analytics/consentimiento
Síntoma: De repente todo es un MISS de caché; la carga en origen se dispara.
Causa raíz: El bypass de caché se dispara por “cualquier cookie” o por una regex amplia que coincide con cookies de consent/analytics.
Solución: Cambia de “omitir si existe cualquier cookie” a un modelo allowlist/denylist: omite solo en cookies funcionales (carrito/sesión/auth). Elimina cookies irrelevantes de la consideración de caché cuando sea seguro.
6) La purga causa caídas o timeouts
Síntoma: El sitio se ralentiza tras actualizaciones de contenido; picos de 5xx; CPU de base de datos sube.
Causa raíz: Estrategia de purge-all; crawler de preloading demasiado agresivo; stampede de caché al expirar.
Solución: Purga selectivamente; implementa stale-while-revalidate donde sea posible; rate-limita preloading; asegura capacidad en el origen para ráfagas de misses.
7) Páginas de producto muestran idioma o moneda incorrectos
Síntoma: Visitantes ven idioma/moneda que no coincide con su selección.
Causa raíz: CDN/proxy cachea HTML sin variar por cookie o cabecera de idioma/moneda; parámetros de query se eliminan incorrectamente.
Solución: Varia la clave de caché por el selector funcional (cookie/cabecera/param). O omite caché en páginas donde no se puede hacer bien.
8) “Funciona cuando evito la caché”
Síntoma: El bug desaparece con caché desactivado; reaparece con caché activado.
Causa raíz: El caché está enmascarando una dependencia con estado (nonce/sesión), o cachea una respuesta de error.
Solución: Evita cachear estados de error; asegura bypass en cookies con estado; ajusta Cache-Control para páginas sensibles; valida capa por capa con cabeceras.
Listas de comprobación / plan paso a paso para caché segura
Paso 1: Inventario de lo que debe ser dinámico
- Lista de URLs transaccionales: carrito, checkout, cuenta, login, admin.
- Lista de páginas de formularios y endpoints de envío (admin-ajax, rutas REST).
- Lista de funciones de personalización en páginas anónimas (visto recientemente, precios geo, selectores de moneda/idioma).
Paso 2: Elige tu autoridad de caché
- Si controlas NGINX/Varnish: úsalo para caché HTML y deja los plugins de page cache de WordPress apagados (o configúralos solo para gestionar caché del navegador/optimización estática).
- Si estás en hosting limitado: usa un plugin de caché reputado y mantén el caché HTML del CDN conservador.
Paso 3: Implementa exclusiones estrictas
- Excluir
/wp-admin/,/wp-login.php. - Excluir
/cart/,/checkout/,/my-account/. - Excluir endpoints AJAX de WooCommerce (
wc-ajax) y fragments. - Excluir endpoints de formularios y páginas con nonces si no puedes renderizarlos dinámicamente.
Paso 4: Implementa bypass basado en cookies
- Omitir en
wordpress_logged_in_*y cualquier cookie de auth/sesión que uses. - Omitir en cookies de carrito/sesión de WooCommerce:
woocommerce_items_in_cart,woocommerce_cart_hash,wp_woocommerce_session_*. - No omitas por “cualquier cookie”. Usa coincidencia dirigida.
Paso 5: Decide claves de variación para idioma/moneda/geo
- Si moneda/idioma cambia el HTML, varía la clave de caché o omite.
- Elimina parámetros de marketing de la clave de caché; conserva los funcionales.
Paso 6: Establece TTLs como un adulto
- Páginas de marketing/blog: 5–30 minutos es un punto de partida común.
- Páginas de producto: TTL más corto si inventario/precios cambian a menudo; si no, TTL moderado con purge en actualización.
- Páginas transaccionales: no-store/no-cache.
Paso 7: Implementa purga selectiva
- Purgar solo las URLs cambiadas cuando se actualiza un post/producto.
- Evitar purge-all salvo en despliegues o emergencias.
- Rate-limit las peticiones de purga para proteger caches y origen.
Paso 8: Añade observabilidad para el caché
- Añade
X-Cachey, idealmente,X-Cache-Bypass-Reasonen la capa de proxy. - Monitoriza la ratio de aciertos de caché, tiempo de respuesta del origen y tasas de 5xx.
- Crea una prueba canaria sintética de /cart/ y /checkout/ que corra cada pocos minutos y alerte si esas páginas se vuelven cacheables.
Paso 9: Prueba con cookies y flujos reales
- Navegación anónima sin cookies.
- Anónimo con cookie de carrito presente.
- Usuario logueado.
- Variaciones de país/moneda si las soportas.
- Enviar formulario desde una página abierta 30+ minutos (escenario de expiración de nonce).
Preguntas frecuentes
1) ¿Puedo cachear páginas de WooCommerce en absoluto?
Puedes cachear algunas páginas de WooCommerce para usuarios anónimos: listados de productos y páginas de detalle suelen estar bien. No cachees carrito, checkout, cuenta ni endpoints AJAX de WooCommerce. El límite de seguridad es el estado de la sesión, no “si es una página de tienda”.
2) ¿Por qué mi tasa de acierto de caché cae casi a cero cuando activo WooCommerce?
Normalmente porque algo está estableciendo cookies de sesión/carrito demasiado pronto (en vistas de producto, homepage o incluso en cada página). Una vez que una cookie está presente, tu lógica de bypass puede omitir el caché para ese usuario. Encuentra qué plugin/tema inicia sesiones y detén eso.
3) ¿Debo omitir caché si existe cualquier cookie?
No. Es la forma rápida de convertir tu caché en un archivo de configuración decorativo. Omite solo en cookies funcionales (auth, carrito, sesión, moneda/idioma si no puedes variar con seguridad).
4) Mi CDN dice que “respeta cabeceras del origen”. ¿Por qué está cacheando checkout de todos modos?
Porque “respeta” tiene letreros. Puedes tener una regla de página que lo sobreescribe, o el CDN puede tratar algunos estados de manera distinta, o estás cacheando por defecto y solo omitiendo en rutas que olvidaste. Verifica comparando cabeceras del origen vs edge y probando con cookies.
5) ¿Cuál es la configuración de caché más segura para WordPress + WooCommerce?
Caché conservador de página completa para páginas anónimas únicamente (proxy inverso o plugin), bypass estricto para carrito/checkout/cuenta/auth y cookies de WooCommerce, más Redis object cache. Cachea assets estáticos agresivamente en CDN/navegador.
6) ¿Necesito Varnish si ya tengo un plugin de caché de WordPress?
No necesariamente. Varnish puede ser excelente, pero es otra pieza móvil. Si controlas el servidor y quieres comportamiento predecible a gran escala, Varnish o NGINX fastcgi_cache suele ser más limpio que un plugin. Si no controlas el servidor, un plugin puede ser la opción práctica.
7) ¿Por qué los formularios fallan solo a veces?
Porque las fallas de caché dependen del tiempo: ventanas de expiración de nonces, TTL de caché y si el usuario golpea una copia cacheada o dispara un render fresco. Fallos intermitentes en formularios indican fuertemente que HTML con nonce está siendo cacheado demasiado tiempo.
8) ¿El object caching (Redis) es arriesgado para WooCommerce?
En general, es menos arriesgado que el page cache porque no sirve HTML de un usuario a otro. Los principales riesgos son operacionales: disponibilidad de Redis, dimensionamiento de memoria y comportamiento de evicción. Monitorízalo como cualquier dependencia.
9) ¿Cómo sé qué capa sirvió la respuesta cacheada?
Con cabeceras. Añádelas si no las tienes. Cabeceras de estado del CDN, X-Cache del proxy inverso y cabeceras del origen como Cache-Control te dicen dónde mirar. Si no puedes saberlo, no puedes depurar bajo presión.
10) ¿Puedo cachear usuarios logueados con seguridad?
Es posible, pero rara vez vale la pena en WordPress salvo que implementes claves de caché por usuario y aceptes la complejidad. Para la mayoría de sitios, invierte en object cache, consultas eficientes y tuning de PHP-FPM. Mantén el HTML de usuarios logueados dinámico.
Siguientes pasos que puedes hacer esta semana
Si tus carritos o formularios están fallando, no empieces cambiando TTLs. Empieza probando qué capa está sirviendo contenido cacheado y si varía por el estado correcto.
- Añade cabeceras debug de caché en tu proxy inverso (HIT/MISS/BYPASS y una razón de bypass). Te lo agradecerás después.
- Implementa exclusiones estrictas para carrito/checkout/cuenta/login/admin y endpoints AJAX de WooCommerce en cada capa de caché que operes.
- Implementa bypass por cookies para cookies de carrito/sesión de WooCommerce y cookies de usuario logueado de WordPress.
- Audita cookies y parámetros de query usados para moneda/idioma/geo y decide: variar, omitir o rediseñar.
- Despliega Redis object cache (si no lo tienes) y confirma que está realmente activo.
- Crea una prueba sintética que consulte /cart/ y /checkout/ y verifique
Cache-Control: no-storey ausencia de cabeceras de HIT de caché. - Detén el comportamiento purge-all salvo en emergencias. Purga de forma quirúrgica y usa estrategias stale para páginas anónimas si tu proxy lo soporta.
El caché es una herramienta poderosa. Trátalo como tal. Ponte las gafas de seguridad: cabeceras, reglas de bypass y tests que corran cuando no estás mirando.