«WordPress está lento» no es un diagnóstico. Es una queja, como «el coche se siente raro». A veces es un plugin que hace 400 consultas por página. A veces es el servidor jadeando porque la E/S de disco está al máximo. A veces la caché funciona perfectamente… para usuarios anónimos, mientras que el tráfico autenticado se arrastra como si pagara por consulta.
Esta guía es cómo dejar de adivinar. Recorreremos la pila de afuera hacia adentro—red, servidor web, PHP-FPM, base de datos, almacenamiento, plugins—usando comandos que puedes ejecutar hoy y decisiones que puedes tomar según lo que veas.
Guion de diagnóstico rápido
Si solo tienes 20 minutos antes de que alguien empiece a «probar plugins al azar», haz esto. El objetivo es responder una pregunta: ¿a dónde se va el tiempo? El segundo objetivo es probarlo con evidencia, no con sensaciones.
1) Primero: mide TTFB y separa aciertos de caché de fallos
- Comprueba una página pública sin autenticar. Luego una página de usuario autenticado. Compara TTFB y tiempo total.
- Si las páginas públicas son rápidas y las autenticadas lentas, no toques aún la configuración de Nginx. Normalmente es PHP, BD o comportamiento de plugins detrás de cachés evitados.
2) Segundo: revisa la saturación del host (CPU, presión de memoria, E/S de disco)
- CPU alta con iowait bajo: demasiado trabajo de PHP o demasiadas peticiones concurrentes.
- iowait alto: cuello de botella de almacenamiento o la BD hace demasiada E/S aleatoria.
- Actividad de swap: ya vas tarde; arregla primero la presión de memoria.
3) Tercero: revisa el encolado de PHP-FPM y los registros lentos
- Si FPM está saturando trabajadores o encolando peticiones, verás TTFB lento y «empeora bajo carga».
- Activa temporalmente el slowlog de PHP-FPM. Es lo más cercano a una confesión.
4) Cuarto: revisa MySQL por consultas lentas y contención de bloqueos
- Slow query log + processlist + estado InnoDB te dicen si la base de datos es el freno.
- Si ves muchos «Sending data» o consultas largas sobre wp_options, estás en territorio WordPress, no en fallo de kernel.
5) Quinto: identifica qué plugin/tema/ruta de código es costosa
- Usa WP-CLI para desactivar plugins sospechosos rápidamente en staging o durante una ventana controlada.
- Correlaciona: ruta de petición → traza de pila PHP (slowlog) → huellas de consultas (slow query log).
Eso es todo. Cinco pasos. Si los sigues, dejarás de discutir «Apache vs Nginx» y empezarás a arreglar el verdadero cuello de botella.
Algunos datos e historia (útil, no para trivial)
- WordPress nació en 2003 como un fork de b2/cafelog, diseñado para una web más lenta donde el renderizado en servidor y el cacheo de páginas eran la norma.
- El cache de consultas de MySQL solía ser un truco común para WordPress, pero fue desaprobado y eliminado en MySQL moderno porque causaba contención bajo concurrencia.
- WP-Cron no es un cron real; es un mecanismo que «ejecuta tareas programadas en visitas a la página». Con bajo tráfico es poco fiable; con alto tráfico puede ser ruidoso.
- WooCommerce cambió la cultura de rendimiento de WordPress: convirtió «un blog» en «un sistema transaccional», donde la latencia es ingresos y los trabajos en segundo plano importan.
- PHP 7 fue un cambio radical en el rendimiento de WordPress comparado con PHP 5.x. Muchas historias antiguas de «WordPress es lento» realmente eran «PHP era lento».
- Opciones autoload en wp_options se cargan en cada petición. Un conjunto autoload inflado puede castigar cada página, cacheada o no.
- HTTP/2 redujo el dolor de muchos assets pequeños, pero no hizo nada por un TTFB lento en el backend. La gente aún confunde «carga lenta» con «descarga lenta».
- Cache de objetos (Redis/Memcached) ayuda cuando las mismas consultas costosas ocurren repetidamente, pero también puede enmascarar un problema de consultas hasta que la caché churnee.
Define “lento” en números, no en sensaciones
No puedes optimizar lo que no puedes describir. «Lento» necesita al menos tres números:
- TTFB (Time To First Byte): tiempo del backend más la latencia de red hasta el primer byte de respuesta. Aquí aparecen PHP, BD y esperas upstream.
- Tiempo total de carga: incluye imágenes, JS, CSS. Puede ser «backend rápido, frontend pesado». Problema diferente.
- Percentiles bajo carga: p50 es cómo está en un buen día; p95 es cuando tus clientes empiezan a irse; p99 es cuando tu canal de incidentes se activa.
También decide: ¿estamos depurando tráfico público anónimo o rutas autenticadas/administrativas? El cacheo de WordPress suele favorecer páginas anónimas. Las rutas autenticadas tienden a evitar caches y golpear la pila completa cada vez.
Comienza por fuera: aislar TTFB, red, CDN y caché
Antes de hacer SSH a nada, prueba si tienes un problema en el backend o en la entrega del frontend. La trampa más fácil es optimizar la base de datos cuando el CDN está mal configurado y cada petición es un fallo de caché. La segunda trampa más fácil es optimizar el CDN cuando el backend está gastando 4 segundos en generar HTML.
Comprobación de realidad de la caché
Si usas un CDN o proxy inverso (Cloudflare, Fastly, Varnish, caché de Nginx), tu primera pregunta es: ¿estoy obteniendo aciertos de caché? Busca cabeceras de caché. Si no existen, operas a ciegas. Añádelas o configúralas. Una caché que no puede observarse es solo un rumor.
Diferenciar “primer byte lento” de “descarga lenta”
TTFB alto significa backend o esperas upstream. Descarga lenta significa tamaño del payload o cuello de botella del cliente. Si tu TTFB es 3 segundos y la página pesa 200 KB, no es el Wi‑Fi del usuario. Eres tú.
Broma #1: Si tu TTFB se mide en segundos, felicidades—has construido un trabajo por lotes muy pequeño y lo expusiste accidentalmente como un sitio web.
Cuellos de botella del servidor: CPU, memoria, E/S de disco y saturación
Cuando WordPress se pone lento, el servidor casi siempre te dice por qué. Solo necesitas preguntar en el orden correcto. Regla SRE: mide la saturación primero, porque la saturación genera síntomas “aleatorios” en todas partes.
CPU: cuando PHP se convierte en un calefactor
CPU alta con iowait bajo normalmente significa que PHP está ocupado: plugins pesados, plantillas costosas, demasiadas peticiones concurrentes o una manada de peticiones por fallos de caché.
Pero ten cuidado: «CPU al 100%» en una VM pequeña puede simplemente significar que estás ejecutando una tienda en producción en una máquina dimensionada para un blog personal. Respeto el optimismo.
Presión de memoria: el asesino de rendimiento con buena relación pública
Poca memoria libre no es inherentemente mala; Linux usa memoria para caché. La mala señal es la actividad de swap o un aumento de fallos de página mayores. Si la máquina está haciendo swap, tu latencia se vuelve irregular. Si hace swap bajo carga, tu sitio comienza a «fallar» aleatoriamente.
E/S de disco: iowait es el olor, no el fuego
iowait alto significa que la CPU espera el almacenamiento. WordPress puede desencadenar dolor de E/S a través de:
- MySQL realizando lecturas aleatorias porque faltan índices o el buffer pool es demasiado pequeño.
- Muchas lecturas de archivos PHP pequeños en almacenamiento de red lento.
- Logging a ritmos absurdos (logs de acceso, logs de depuración, slow logs en sitios ocupados sin rotación).
- Backups o antivirus saturando discos.
Cache del kernel y sistema de archivos: rápido hasta que no lo es
La caché de páginas de Linux hace las lecturas repetidas rápidas—hasta que la presión de memoria fuerza la expulsión. Si el rendimiento cae en picado tras un despliegue o un flush de caché, y luego «se calienta» lentamente, eso puede ser comportamiento de la caché del sistema de archivos o de la caché de la aplicación/objeto. Arreglos diferentes, síntomas similares.
Servidor web y PHP-FPM: los sospechosos habituales
En una pila moderna de WordPress, la petición normalmente fluye: Nginx/Apache → PHP-FPM → WordPress → MySQL → retorno. Tu cuello de botella suele aparecer como encolamiento en algún punto de esa cadena.
Servidor web: mantenlo aburrido
Nginx y Apache pueden servir WordPress bien. La mayoría de problemas de “rendimiento del servidor web” no son sobre cuál elegiste; son sobre mala configuración, falta de caché o sobrecarga TLS en hardware insuficiente.
PHP-FPM: donde el encolamiento se vuelve visible para el usuario
PHP-FPM tiene un modo de fallo simple: demasiadas peticiones concurrentes, no suficientes workers, y cada worker está ocupado demasiado tiempo. Eso crea una cola. La cola genera latencia. Entonces el balanceador reintenta. Entonces obtienes más tráfico. Entonces todos aprenden nuevas palabras.
El slow log de PHP-FPM es tu amigo. Señala la ruta de código. También acaba con las discusiones. Si el slow log dice que el 70% del tiempo se gasta en la llamada a la API de un plugin, no necesitas un debate filosófico sobre opcache.
OPcache: la victoria de bajo riesgo
OPcache no es opcional en WordPress en producción. Sin él, recompilas scripts PHP constantemente. Eso no es «dinámico». Es desperdicio. Asegúrate de que está activado, dimensionado sensatamente y que no se reinicia constantemente.
Base de datos: bloqueos en MySQL/MariaDB y patologías de consultas
WordPress usa MySQL como un caballo de carga, y a veces como una mula alquilada. La base de datos suele ser el cuello de botella porque es compartida, está infra-dimensionada o se le piden consultas patológicas.
Puntos clásicos de dolor en la base de datos de WordPress
- Inflado de autoload en wp_options: opciones autoload enormes hacen que cada petición arrastre una maleta de datos desde la BD a la memoria PHP.
- Consultas meta sin índices: wp_postmeta y wp_usermeta pueden convertirse en cementerios de consultas.
- Consultas de pedidos en WooCommerce: joins complejos y búsquedas en meta pueden destrozar almacenamiento lento.
- Bloqueos: transacciones largas u operaciones ALTER pueden bloquear lecturas/escrituras y producir picos.
Slow query log: el mejor documento de “por qué está lento” que generarás
Activa el registro de consultas lentas con un umbral bajo temporalmente (digamos 0.5–1s) para capturar a los peores ofensores. Luego agrupa por fingerprint (forma de la consulta), no por texto exacto. La misma consulta repetida es el verdadero centro de costo.
Buffer pool de InnoDB: la palanca de memoria
Si el buffer pool es pequeño respecto al conjunto de trabajo, MySQL se convierte en generador de E/S. En hosts dedicados, el buffer pool suele ajustarse a ~60–75% de la RAM. En hosts compartidos, debes ser conservador o MySQL peleará con el SO y perderá.
Cita (idea parafraseada): «La esperanza no es una estrategia.» — atribuida comúnmente en cultura de operaciones y ligada al pensamiento de ingeniería de confiabilidad.
Capa WordPress y plugins: demostrar quién es culpable
Los plugins son código que no escribiste, ejecutándose con los mismos privilegios que tu código. Trátalos como proveedores con acceso a shell: asume que harán algo sorprendente, eventualmente.
Patrones costosos que puedes detectar realmente
- Demasiadas consultas por petición: especialmente en pantallas de administración y WooCommerce.
- Llamadas HTTP externas en carga de página: píxeles de marketing, comprobaciones de licencias, llamadas a APIs que «llaman a casa».
- Lógica de plantillas sin caché: bucles que desencadenan consultas N+1.
- Basura autoload: almacenar arrays grandes en options con autoload=yes.
- Búsquedas y filtrados: búsqueda mal indexada, LIKE con comodines, consultas meta sin restricciones.
No «optimices» añadiendo más plugins
Los plugins de caché pueden ayudar, pero apilarlos es un hobby, no un plan de ingeniería. Una caché de páginas, una caché de objetos, una estrategia CDN. Elige deliberadamente. Observa. Itera.
Broma #2: Cada vez que instalas «Ultimate Speed Booster Pro», una consulta lenta recibe sus alas.
WP-Cron y trabajo en segundo plano: el impuesto oculto
WP-Cron ejecuta tareas programadas cuando alguien visita el sitio. Eso significa:
- En sitios de bajo tráfico, las tareas pueden no ejecutarse a tiempo.
- En sitios de alto tráfico, las tareas pueden ejecutarse con demasiada frecuencia, solaparse y provocar picos de CPU/BD.
Si tu sitio se vuelve lento «cada pocos minutos», WP-Cron es un sospechoso principal. También lo son backups, importaciones, cache warmers, tareas de optimización de imágenes y sincronizaciones de newsletters.
La medida adulta es desactivar el disparo de WP-Cron en cargas de página y ejecutarlo mediante cron del sistema con una cadencia controlada.
Tareas prácticas (comandos + qué significa la salida + la decisión que tomas)
Estos son chequeos estilo producción. Ejecútalos en orden. Cada uno te dice qué hacer después. Ese es el punto.
Task 1: Measure TTFB and total time from the server edge
cr0x@server:~$ curl -s -o /dev/null -w "namelookup:%{time_namelookup}\nconnect:%{time_connect}\nstarttransfer:%{time_starttransfer}\ntotal:%{time_total}\n" https://example.com/
namelookup:0.004
connect:0.021
starttransfer:1.842
total:1.913
Significado: starttransfer es básicamente TTFB (más la red). Aquí es ~1.84s, así que el backend está lento.
Decisión: Si starttransfer es alto pero connect es bajo, deja de culpar al DNS/CDN. Pasa a servidor/PHP/BD.
Task 2: Compare anonymous vs logged-in/admin response time
cr0x@server:~$ curl -s -o /dev/null -w "TTFB:%{time_starttransfer} total:%{time_total}\n" https://example.com/wp-admin/
TTFB:3.912 total:4.105
Significado: wp-admin es mucho más lento. Probablemente se evita la caché; el trabajo de PHP/BD es mayor.
Decisión: Prioriza la inspección de PHP-FPM y BD; no pierdas tiempo ajustando el cacheo de assets estáticos.
Task 3: Check system load, CPU, and iowait quickly
cr0x@server:~$ uptime
14:22:18 up 36 days, 2:03, 1 user, load average: 8.72, 7.91, 6.88
cr0x@server:~$ mpstat -P ALL 1 3
Linux 6.5.0 (server) 12/27/2025 _x86_64_ (4 CPU)
Average: CPU %usr %nice %sys %iowait %irq %soft %steal %idle
Average: all 62.11 0.00 7.42 18.93 0.00 0.37 0.00 11.17
Significado: el load average es alto respecto a 4 CPUs, y el iowait es ~19%. El disco está contribuyendo.
Decisión: Revisa latencia de disco y patrones de E/S de MySQL a continuación; no aumentes solo workers de PHP.
Task 4: See if memory pressure or swap is involved
cr0x@server:~$ free -h
total used free shared buff/cache available
Mem: 8.0Gi 6.7Gi 220Mi 210Mi 1.1Gi 680Mi
Swap: 2.0Gi 1.2Gi 820Mi
cr0x@server:~$ vmstat 1 5
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
r b swpd free buff cache si so bi bo in cs us sy id wa st
3 1 1249280 225384 9120 988224 10 24 612 430 510 890 62 7 12 19 0
Significado: se está usando swap y hay intercambio en curso (si/so). Eso provoca picos de latencia.
Decisión: Reduce la presión de memoria: dimensiona bien la VM, reduce PHP-FPM max children, ajusta memoria de MySQL o añade RAM antes de micro-optimizaciones.
Task 5: Identify top CPU and memory consumers
cr0x@server:~$ ps -eo pid,comm,%cpu,%mem --sort=-%cpu | head
2841 php-fpm8.2 38.2 3.1
2910 php-fpm8.2 34.9 3.0
1732 mysqld 21.4 24.8
1120 nginx 3.1 0.4
Significado: PHP-FPM consume CPU, MySQL tiene mucho uso de memoria (podría estar bien) y CPU no trivial.
Decisión: Inspecciona saturación de workers PHP-FPM y comportamiento de consultas MySQL en paralelo.
Task 6: Check disk latency and queue depth
cr0x@server:~$ iostat -x 1 3
Linux 6.5.0 (server) 12/27/2025 _x86_64_ (4 CPU)
Device r/s w/s rMB/s wMB/s avgrq-sz avgqu-sz await r_await w_await %util
nvme0n1 52.0 38.0 3.1 5.7 175.2 4.21 28.40 21.10 37.20 94.8
Significado: await ~28ms y %util ~95% significa que el disco está saturado. MySQL y lecturas de archivos PHP sufrirán.
Decisión: Reduce E/S: arregla consultas lentas/índices, aumenta buffer pool de MySQL si hay memoria, mueve BD a almacenamiento más rápido o separa el host de BD.
Task 7: Check PHP-FPM pool status for queueing (if enabled)
cr0x@server:~$ sudo curl -s http://127.0.0.1/status?full | sed -n '1,25p'
pool: www
process manager: dynamic
start time: 27/Dec/2025:13:55:21 +0000
start since: 1602
accepted conn: 19482
listen queue: 37
max listen queue: 211
listen queue len: 128
idle processes: 0
active processes: 24
total processes: 24
max active processes: 24
max children reached: 19
slow requests: 83
Significado: la cola de escucha no es cero y «max children reached» es alto. Las peticiones esperan por workers.
Decisión: No aumentes ciegamente max children. Primero reduce el coste por petición (slowlog, BD) y confirma margen de memoria; si no, solo amplificas el swap y empeoras.
Task 8: Enable and read PHP-FPM slowlog (temporary, targeted)
cr0x@server:~$ sudo grep -nE 'slowlog|request_slowlog_timeout' /etc/php/8.2/fpm/pool.d/www.conf
261:request_slowlog_timeout = 2s
262:slowlog = /var/log/php8.2-fpm.slow.log
cr0x@server:~$ sudo tail -n 20 /var/log/php8.2-fpm.slow.log
[27-Dec-2025 14:20:11] [pool www] pid 2910
script_filename = /var/www/html/index.php
[0x00007f2a8c1a3f60] wp_remote_get() /var/www/html/wp-includes/http.php:271
[0x00007f2a8c1a3df0] some_plugin_license_check() /var/www/html/wp-content/plugins/some-plugin/core.php:812
Significado: PHP está bloqueado en una llamada HTTP saliente dentro de un plugin.
Decisión: Arregla el comportamiento del plugin (desactiva, configura, cachea resultados, move a asincrónico). Aumentar CPU no arreglará esperas en la red.
Task 9: Confirm OPcache is enabled and not tiny
cr0x@server:~$ php -i | grep -E 'opcache.enable|opcache.memory_consumption|opcache.max_accelerated_files' | head -n 5
opcache.enable => On => On
opcache.memory_consumption => 256 => 256
opcache.max_accelerated_files => 20000 => 20000
Significado: OPcache está activado y dimensionado razonablemente para muchos archivos WP.
Decisión: Si OPcache está apagado, actívalo. Si la memoria es muy pequeña, verás reinicios frecuentes de caché y CPU extra.
Task 10: Check MySQL currently running queries and lock symptoms
cr0x@server:~$ sudo mysql -e "SHOW FULL PROCESSLIST\G" | sed -n '1,60p'
*************************** 1. row ***************************
Id: 2198
User: wp
Host: 127.0.0.1:49822
db: wordpress
Command: Query
Time: 12
State: Sending data
Info: SELECT option_name, option_value FROM wp_options WHERE autoload = 'yes'
*************************** 2. row ***************************
Id: 2202
User: wp
Host: 127.0.0.1:49836
db: wordpress
Command: Query
Time: 9
State: statistics
Info: SELECT * FROM wp_postmeta WHERE meta_key = '_price' AND meta_value BETWEEN '10' AND '50'
Significado: Consultas de larga duración. La consulta de autoload debería ser rápida; si no lo es, wp_options está inflado o el servidor está limitado por E/S. La consulta sobre postmeta es un punto clásico de dolor.
Decisión: Activa slow query log e inspecciona índices/tamaño de autoload en options. Considera estrategias específicas de indexado para WooCommerce y reducir consultas meta.
Task 11: Enable slow query logging (short window) and inspect it
cr0x@server:~$ sudo mysql -e "SET GLOBAL slow_query_log = 'ON'; SET GLOBAL long_query_time = 0.5; SET GLOBAL log_output = 'FILE';"
cr0x@server:~$ sudo tail -n 20 /var/log/mysql/mysql-slow.log
# Time: 2025-12-27T14:24:02.118532Z
# User@Host: wp[wp] @ localhost [] Id: 2251
# Query_time: 1.734 Lock_time: 0.000 Rows_sent: 1 Rows_examined: 874221
SET timestamp=1766845442;
SELECT * FROM wp_postmeta WHERE meta_key = '_price' AND meta_value BETWEEN '10' AND '50';
Significado: Rows_examined es enorme relativo a filas devueltas. Ese es un patrón de índice faltante/esquema equivocado.
Decisión: Deja de ajustar tamaños de buffers como primer movimiento. Arregla la ruta de la consulta: ajusta el comportamiento del plugin, reduce filtrado por meta, añade índices adecuados donde sea seguro, o usa tablas de búsqueda dedicadas cuando sea posible.
Task 12: Check InnoDB buffer pool effectiveness
cr0x@server:~$ sudo mysql -e "SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_read%';"
+---------------------------------------+-----------+
| Variable_name | Value |
+---------------------------------------+-----------+
| Innodb_buffer_pool_read_requests | 987654321 |
| Innodb_buffer_pool_reads | 3456789 |
+---------------------------------------+-----------+
Significado: Buffer pool reads indica lecturas desde disco; requests son lecturas lógicas. Si reads es alto respecto a requests, tu conjunto de trabajo no cabe en memoria.
Decisión: Si tienes RAM disponible, aumenta innodb_buffer_pool_size. Si no, reduce el conjunto de trabajo (limpia autoloads, arregla consultas, archiva datos antiguos).
Task 13: Find autoloaded options size (classic silent killer)
cr0x@server:~$ sudo mysql -D wordpress -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 opciones autoload es masivo; esto se carga en memoria con frecuencia y puede ralentizar cada petición no cacheada.
Decisión: Identifica las opciones autoload más grandes y arregla la fuente (ajustes de plugins, uso incorrecto de transients). Apunta a MB de un solo dígito o menos.
Task 14: Identify biggest autoloaded options (so you know who to yell at)
cr0x@server:~$ sudo mysql -D wordpress -e "SELECT option_name, autoload, ROUND(LENGTH(option_value)/1024,1) AS kb FROM wp_options WHERE autoload='yes' ORDER BY LENGTH(option_value) DESC LIMIT 10;"
+-------------------------------+----------+------+
| option_name | autoload | kb |
+-------------------------------+----------+------+
| some_plugin_big_settings | yes | 5120 |
| rewrite_rules | yes | 980 |
| another_plugin_cache_blob | yes | 740 |
+-------------------------------+----------+------+
Significado: Un plugin está autoloading blobs de varios megabytes. Eso no son «ajustes», es un grito de ayuda.
Decisión: Reconfigura/reemplaza el plugin, o cambia autoload a no para opciones específicas (con cuidado, tras validación). También vacía y reconstruye rewrite rules si esa entrada está inflada.
Task 15: Check whether WP-Cron is hammering requests
cr0x@server:~$ wp cron event list --fields=hook,next_run,recurrence --format=table
+------------------------------+---------------------+------------+
| hook | next_run | recurrence |
+------------------------------+---------------------+------------+
| wp_version_check | 2025-12-27 14:25:00 | twice_daily|
| some_plugin_sync_job | 2025-12-27 14:23:00 | every_min |
| woocommerce_cleanup_sessions | 2025-12-27 14:26:00 | hourly |
+------------------------------+---------------------+------------+
Significado: Jobs «every_min» pueden ser legítimos, pero a menudo se solapan y causan picos periódicos.
Decisión: Mueve cron a cron del sistema, reduce la frecuencia y asegura que los trabajos largos tengan locking para evitar solapamientos.
Task 16: Quickly test plugin impact (staging or controlled window)
cr0x@server:~$ wp plugin list --status=active --format=table
+---------------------+--------+-----------+---------+
| name | status | update | version |
+---------------------+--------+-----------+---------+
| woocommerce | active | available | 8.4.0 |
| some-plugin | active | none | 3.2.1 |
| seo-suite | active | none | 19.0 |
+---------------------+--------+-----------+---------+
cr0x@server:~$ wp plugin deactivate some-plugin
Plugin 'some-plugin' deactivated.
Significado: Puedes validar si el culpable del slowlog realmente impulsa la latencia.
Decisión: Si la latencia baja materialmente, manténlo desactivado y planifica un reemplazo más seguro o una corrección del proveedor. Si no cambia, reactívalo y sigue buscando.
Tres mini-historias corporativas (cómo falla esto en la vida real)
Mini-historia 1: El incidente causado por una suposición errónea
Una empresa manejaba un sitio de contenidos WordPress con una tienda WooCommerce separada. El sitio de contenidos empezó a tener timeouts durante una campaña. Todos asumieron que el CDN era el problema porque «cambiamos reglas de caché la semana pasada». Sonaba plausible. También estaba equivocado.
El ingeniero on-call hizo lo aburrido: curl timing desde múltiples regiones, luego SSH, luego iostat. El TTFB era alto incluso desde dentro del VPC. %util del disco estaba al máximo, iowait alto. MySQL estaba local en la misma VM. El CDN era inocente; solo estaba entregando respuestas lentas fielmente.
La suposición errónea fue que «las páginas estáticas deberían estar cacheadas, así que el origen no puede ser el problema». En realidad, editores autenticados estaban golpeando endpoints no cacheados y disparando cargas autoload de opciones costosas en cada petición. La campaña aumentó la actividad administrativa: más borradores, revisiones, vistas previas y llamadas API impulsadas por plugins.
La solución no fue heroica: reducir el inflado de autoload, separar la BD en una instancia más rápida, y añadir observabilidad sobre tasas de acierto de caché y profundidad de cola de PHP-FPM. El incidente terminó cuando dejaron de discutir sobre el edge y empezaron a medir el origen.
Mini-historia 2: La optimización que salió mal
Otro equipo tenía páginas de checkout lentas y decidió «resolverlo con caché». Añadieron una capa de caché de página y ajustaron TTLs agresivamente. Las páginas de producto anónimas se vieron genial. Los gráficos del dashboard mejoraron. La gente celebró.
Luego los tickets de soporte se dispararon. Los compradores vieron inventario obsoleto y precios inconsistentes. Los usuarios autenticados seguían lentos, y ahora el sistema era más difícil de razonar porque las cachés enmascaraban problemas de backend. La caché de página también estaba cacheando fragmentos personalizados incorrectamente. Nada como desplegar una mejora de rendimiento que también despliega confusión.
Cuando finalmente habilitaron el slowlog de PHP-FPM, el culpable fue un plugin que llamaba APIs externas de impuestos/envío de forma síncrona durante el checkout, sin disciplina de timeouts y sin caché de resultados. La «optimización» no tocó ese camino; solo hizo algunos gráficos más verdes.
Retrocedieron la caché agresiva, implementaron timeouts sensatos, añadieron fallback asincrónico donde las reglas de negocio lo permitían, y cachearon respuestas de API con claves cuidadosas. El rendimiento mejoró, la coherencia volvió y todos aprendieron la misma lección: la caché no sustituye a entender.
Mini-historia 3: La práctica aburrida pero correcta que salvó el día
Un medio de comunicación ejecutaba WordPress a tráfico alto sostenido. Nada glamuroso: ciclos de noticias, muchas lecturas, picos ocasionales. Su arma secreta no era un plugin mágico. Era disciplina.
Tenían slow query logging activado con un umbral razonable, rotaban y enviaban logs, y revisaban fingerprints de consultas top semanalmente. No durante incidentes. Semanalmente. También monitorizaban «max children reached» de PHP-FPM y tasa de aciertos del buffer pool de MySQL como métricas de salud estándar.
Un viernes por la tarde, la latencia empezó a subir. Aún no había outage—solo un sutil aumento del p95. Porque tenían baselines, el on-call vio inmediatamente que las lecturas del buffer pool subían y la espera de disco aumentaba. Se correlacionó con un despliegue de una nueva característica en un plugin que introdujo una consulta meta en datos de alta cardinalidad.
Revirtieron el cambio del plugin, añadieron un índice en staging, verificaron el plan de consultas y volvieron a desplegar. Los usuarios apenas lo notaron. El equipo se fue a casa. La observabilidad no les hizo teclear más rápido; les hizo menos sorprendidos.
Errores comunes: síntoma → causa raíz → solución
1) Síntoma: la página principal es rápida, wp-admin es dolorosamente lento
Causa raíz: el cache de páginas ayuda a usuarios anónimos; admin evita la caché y dispara consultas DB pesadas, llamadas externas o autoload bloat.
Solución: activa PHP-FPM slowlog; revisa tamaño de autoload; audita plugins que enganchan pantallas admin; añade cache de objetos; reduce autoload en wp_options.
2) Síntoma: el rendimiento es bueno hasta que hay un pico de tráfico, luego todo falla
Causa raíz: saturación y encolamiento: PHP-FPM max children alcanzado, límites de conexiones DB, o E/S de disco al máximo.
Solución: mide profundidad de cola (FPM status), aumenta capacidad con cuidado (más CPU/RAM/disco rápido), añade caché y reduce trabajo por petición.
3) Síntoma: lentitud periódica cada pocos minutos
Causa raíz: WP-Cron o trabajos programados solapándose, backups, rotación de logs fallando o tareas de sincronización externas.
Solución: mueve WP-Cron a cron del sistema; añade locking; programa trabajos pesados fuera de horas pico; monitoriza duración de cron.
4) Síntoma: CPU baja, pero las páginas tardan segundos en empezar a cargar
Causa raíz: I/O bloqueante: disco lento, DNS lento en llamadas salientes, APIs externas o esperas de BD.
Solución: revisa iowait/iostat; inspecciona slowlog de PHP para wp_remote_*; añade timeouts y caché para llamadas externas.
5) Síntoma: después de instalar un plugin, TTFB se duplica
Causa raíz: el plugin añade hooks costosos, opciones autoload o consultas meta pesadas en cada petición.
Solución: desactívalo y confirma; inspecciona slowlog y slow query log; reemplaza el plugin o cambia configuración; limpia autoload.
6) Síntoma: CPU de la base de datos alta, muchas consultas “Sending data”
Causa raíz: consultas no indexadas, escaneos de tablas grandes o Rows_examined alto en el slow log. A veces causado por búsquedas/filtrados.
Solución: revisa fingerprints de consultas lentas; añade/ajusta índices con cautela; reduce uso de meta-queries; considera modelado alternativo para atributos de producto.
7) Síntoma: rápido después de reiniciar, lento después
Causa raíz: leaks de memoria o crecimiento de caché (cache de objetos, fragmentación de opcache), o expulsión del buffer pool por crecimiento del conjunto de trabajo.
Solución: vigila memoria en el tiempo; confirma ajustes de OPcache; asegura tamaño apropiado del buffer pool de MySQL; limita caches y purga inteligentemente.
Listas de verificación / plan paso a paso
Checklist A: triage de 30 minutos (seguro en producción)
- Mide TTFB y tiempo total para una URL pública y una URL admin.
- Revisa saturación del host: uptime, mpstat, vmstat, iostat.
- Inspecciona estado de PHP-FPM: longitud de cola, max children reached, requests lentos.
- Revisa processlist de MySQL para consultas de larga duración; captura estado InnoDB si es necesario.
- Si disco está saturado: prioriza arreglos de consultas DB y mejoras de almacenamiento sobre “más workers PHP”.
- Si CPU en PHP está saturada: activa slowlog y encuentra la ruta de código.
- Anota: ¿qué cambió recientemente (actualizaciones de plugins, cambios de tema, crecimiento de BD, cambio de tráfico)?
Checklist B: plan de estabilización de 1–2 días
- Activa slow query log (umbral 0.5–1s) durante ventanas pico; recoge fingerprints de consultas top.
- Activa PHP-FPM slowlog a 2s temporalmente; identifica stacks lentos.
- Audita tamaño de autoload en wp_options; redúcelo agresiva pero seguramente.
- Mueve WP-Cron a cron del sistema; evita solapamientos.
- Confirma tamaño de OPcache y recuento de workers PHP-FPM acorde a la RAM disponible.
- Añade cache de objetos (Redis) si tienes patrones repetidos y tasas de acierto estables.
- Prueba cambios en staging con volumen de datos similar a producción. «Funciona en una instalación limpia» no es una prueba de rendimiento.
Checklist C: mejoras estructurales (lo que evita incidentes recurrentes)
- Separa la BD del web si la contención de recursos es real y constante.
- Pon la BD en almacenamiento rápido y con baja latencia; evita discos en red lentos para stores con muchas escrituras.
- Instrumenta tasa de aciertos/misses de caché, cola de PHP-FPM y latencia DB como métricas de primera clase.
- Establece una política de plugins: propietarios, cadencia de actualizaciones, plan de rollback y presupuesto de rendimiento.
- Poda regularmente revisiones, transients, sesiones y meta huérfanos según necesidades del negocio.
- Realiza pruebas de carga para lanzamientos importantes con concurrencia realista y flujos autenticados, no solo hits anónimos a la homepage.
Preguntas frecuentes
1) ¿Cómo sé si el cuello de botella es el servidor o la base de datos?
Mira iowait, espera de disco y consultas lentas de MySQL. Si iostat muestra await/%util altos y el slow log de MySQL muestra Rows_examined altos, la combinación BD+disco probablemente es el freno.
2) Mi CPU está baja, pero WordPress sigue lento. ¿Cómo puede ser?
Esperar no consume CPU. Esperas de disco, de red (APIs externas) y bloqueos pueden producir un TTFB alto con CPU baja. El slowlog de PHP-FPM suele revelar llamadas bloqueantes.
3) ¿Debo aumentar PHP-FPM max_children para arreglar la lentitud?
Sólo si tienes margen de RAM y tus peticiones ya son eficientes. Si estás haciendo swap o el disco está limitado, más workers aumentan la contención y empeoran la latencia.
4) ¿Redis hace automáticamente rápido WordPress?
No. Redis ayuda cuando hay búsquedas costosas repetidas y se pueden cachear con seguridad. No arreglará consultas sin índice que son únicas cada vez, ni plugins que hacen llamadas externas.
5) ¿Por qué wp-admin es más lento que el frontend?
Las pantallas de admin suelen evitar caches de página, ejecutan más consultas, cargan más código y disparan hooks de plugins. Además: las cookies de usuario suelen evitar cache CDN por diseño.
6) ¿Cuál es la forma más rápida de identificar un plugin malo?
Slowlog de PHP-FPM para stack traces más WP-CLI para desactivar plugins selectivamente (preferible en staging). Correlaciona con consultas lentas en BD. No te fíes de «parece que empezó después de…» sin evidencia.
7) ¿WooCommerce es intrínsecamente lento?
No intrínsecamente, pero es fácil hacerlo lento porque almacena muchos datos estructurados en tablas meta y dispara consultas complejas. Las rutas de checkout también son sensibles a integraciones externas.
8) ¿Por qué las cosas se ponen lentas periódicamente, como por reloj?
WP-Cron, backups, logrotate, trabajos de indexación, sincronizaciones de producto o purgas de caché. Correlaciona con logs de cron y actividad del sistema (picos en iostat, picos en MySQL) en esos momentos.
9) Si paso a un servidor más grande, ¿se arregla todo?
Puedes ganar tiempo, y a veces es la decisión correcta. Pero si tienes una consulta patológica que escanea millones de filas, volverá cuando los datos crezcan otra vez—a menudo en el peor momento.
10) ¿Debería usar Nginx o Apache por rendimiento?
Elige el que puedas operar bien. La mayoría de la lentitud de WordPress está corriente arriba (PHP/BD/plugins) o en la estrategia de caché. La elección del servidor web rara vez es el verdadero cuello de botella.
Próximos pasos (qué hacer el lunes por la mañana)
- Mide TTFB para rutas públicas y admin y anota los números. Si no puedes cuantificar, no puedes mejorar.
- Revisa la saturación primero: CPU, presión de memoria, latencia de disco. Arregla swap y discos al máximo antes de afinar ajustes de plugins.
- Activa las dos verdades (temporalmente, con cuidado): PHP-FPM slowlog y MySQL slow query log.
- Arregla las piedras grandes: inflado de autoload, fingerprints de consultas lentas, llamadas externas síncronas y caos de WP-Cron.
- Hazlo observable: cola de FPM, tasa de aciertos de caché, latencia DB. Si no lo ves, lo revivirás.
El rendimiento de WordPress no es misterioso. Es por capas. Pela la cebolla en el orden correcto y encontrarás el cuello de botella sin sacrificar un fin de semana a la superstición.