Nuestro servidor de 16 cores llegó a un load average de 66. Pasamos horas descartando hackeos, malware y mineros hasta que revisamos los access logs: el crawler de Facebook estaba haciendo requests con add-to-cart a nuestra tienda WooCommerce, saltándose el caché y acumulando procesos PHP hasta tumbar el servidor. Se arregló con 7 líneas en .htaccess. La CPU pasó del 1500% al 3%.
El servidor dejó de responder un viernes de fin de mes
Tres tiendas WooCommerce en un servidor Hetzner CPX62 (16 cores, 32 GB RAM, Ubuntu 24.04) con OpenLiteSpeed y RunCloud. Llevaba 135 días de uptime sin un solo problema. Hasta que los tres sitios dejaron de cargar al mismo tiempo.
RunCloud mandó la alerta de recursos al límite. Nos conectamos por SSH y esto fue lo primero que vimos:
$ uptime
load average: 66.20, 58.47, 42.31
Load average de 66 en un servidor de 16 cores. Más de cuatro veces su capacidad.

Lo primero que pensamos: nos hackearon
Es la reacción natural. CPU al 100% sin explicación, un viernes a fin de mes. Hicimos el recorrido completo:
# Buscar mineros de criptomonedas
$ ps aux | grep -iE 'xmrig|kdevtmpfsi|kinsing|crypto|miner'
# Nada
# Buscar ejecutables sospechosos en /tmp
$ find /tmp /dev/shm -type f -executable
# Nada
# Buscar PHP con eval/base64 (backdoors clásicas)
$ grep -r --include='*.php' -l -E 'eval\s*\(\s*(base64_decode|gzinflate)\s*\(' /home/*/webapps/*/wp-content/
# Nada
# Buscar archivos PHP en uploads (típico de malware WordPress)
$ find /home/*/webapps/*/wp-content/uploads -name '*.php' -type f
# Solo index.php de plugins legítimos
# Conexiones salientes sospechosas
$ ss -tunap state established | grep -v '127.0.0.1' | grep -v '::1'
# Solo conexiones normales
# Crontab de root
$ crontab -l
# Vacía
Nada. Servidor limpio. Sin malware, sin mineros, sin backdoors, sin conexiones raras. Pero algo estaba consumiendo los 16 cores.
Todo apuntaba a WordPress
Los procesos que saturaban CPU eran todos lsphp, el proceso PHP de OpenLiteSpeed:
eletech+ 23885 57.8 0.9 508216 302568 ? lsphp:home/eletech-cl/webapps/eletech-cl/index.php
eletech+ 24445 57.1 0.9 521340 309136 ? lsphp:home/eletech-cl/webapps/eletech-cl/index.php
eletech+ 23826 54.6 0.9 507972 301340 ? lsphp
eletech+ 24297 50.6 0.9 520080 313836 ? lsphp
eletech+ 23755 48.5 0.9 503680 296704 ? lsphp:home/eletech-cl/webapps/eletech-cl/index.php
...
Más de 40 procesos PHP de un solo sitio, cada uno entre 30% y 58% de CPU. Y los otros dos sitios sumaban más.
Primera hipótesis: plugins pesados, WP-Cron agresivo, Action Scheduler desbordado. Encontramos parte de eso:
- WP-Cron del sistema ejecutándose cada minuto
- Action Scheduler con colas acumuladas
- Un plugin de facturas que había acumulado 152 tareas duplicadas en 135 días de uptime
Lo corregimos. La carga bajó un poco, pero el servidor seguía arriba de 16 de load. Algo más estaba pasando.
La respuesta estaba en los access logs
Llevábamos horas mirando procesos, plugins, crontabs y tablas de base de datos. El problema se resolvió cuando hicimos algo que debimos hacer desde el principio: leer los access logs con IPs y User-Agents.
$ tail -30 /home/eletech-cl/logs/eletech-cl_access.log
57.141.20.7 - [31/Mar/2026:18:23:08] "GET /tienda/?add-to-cart=136666&ek_pag=11" 200 371806 "meta-externalagent/1.1"
57.141.20.34 - [31/Mar/2026:18:23:10] "GET /tienda/?add-to-cart=45447&ek_pag=5" 200 383128 "meta-externalagent/1.1"
57.141.20.3 - [31/Mar/2026:18:23:09] "GET /tienda/page/18/?add-to-cart=123858" 200 382305 "meta-externalagent/1.1"
57.141.20.49 - [31/Mar/2026:18:23:09] "GET /tienda/?add-to-cart=133561&ek_pag=23" 200 371436 "meta-externalagent/1.1"
57.141.20.56 - [31/Mar/2026:18:23:10] "GET /tienda/?add-to-cart=131079&ek_pag=7" 200 375294 "meta-externalagent/1.1"
57.141.20.0 - [31/Mar/2026:18:23:10] "GET /tienda/?add-to-cart=132411&ek_pag=13" 200 373427 "meta-externalagent/1.1"
57.141.20.30 - [31/Mar/2026:18:23:10] "GET /tienda/page/4/?add-to-cart=133561" 200 374036 "meta-externalagent/1.1"
57.141.20.1 - [31/Mar/2026:18:23:12] "GET /tienda/page/4/?add-to-cart=132799" 200 382181 "meta-externalagent/1.1"
Ahí estaba. El crawler de Facebook (meta-externalagent) estaba recorriendo la tienda siguiendo URLs que contenían add-to-cart. En la práctica, estaba “agregando productos al carrito” una y otra vez, desde decenas de IPs distintas del rango 57.141.20.x, todas al mismo tiempo.

Por qué un bot puede tumbar un servidor con caché
La diferencia entre una visita normal y una con add-to-cart es enorme:
Visitar una página de producto:
Visitante → LiteSpeed Cache → Respuesta en 2ms → Cero PHP
Agregar al carrito:
Visitante → Bypass de caché → WordPress completo →
WooCommerce → Crear sesión → Actualizar carrito → Disparar hooks →
Webhooks → Tracking → ~300MB RAM + 2-4 segundos de CPU
No importa qué tan bueno sea tu caché. Las acciones de WooCommerce (add-to-cart, checkout, ajax) lo saltan por diseño: son operaciones que modifican estado y no se pueden cachear. El crawler de Facebook no sabe eso. Para él, ?add-to-cart=136666 es solo otra URL.
Con decenas de peticiones de este tipo llegando al mismo tiempo, los 35 PHP workers del sitio se llenaban en segundos. Y ahí empezaba la cascada:
- Los workers se ocupan atendiendo al bot
- Las peticiones de clientes reales se encolan
- El cron del sistema dispara cada minuto y necesita más workers
- Action Scheduler intenta procesar colas y hace requests HTTP al propio servidor
- Los procesos del minuto anterior todavía no terminan cuando llegan los nuevos
- El servidor colapsa
Lo peor: la IP con más peticiones en el access log no era el bot de Facebook. Era el propio servidor hablando consigo mismo:
$ awk '{print $1}' access.log | sort | uniq -c | sort -rn | head -5
9464 46.224.42.9 ← El propio servidor (HTTP loopback)
837 190.45.54.88 ← Cliente real
806 181.43.122.171 ← Cliente real
742 45.71.44.72 ← Cliente real
702 190.164.222.26 ← Cliente real
9,464 peticiones a /wp-admin/admin-post.php. WordPress intentando procesar las colas que el bot generaba, haciendo requests a sí mismo, consumiendo workers que ya no tenía disponibles.

La solución
Siete líneas en .htaccess:
# BEGIN Block Meta Crawler add-to-cart
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{HTTP_USER_AGENT} meta-externalagent [NC]
RewriteCond %{QUERY_STRING} add-to-cart [NC]
RewriteRule .* - [F,L]
</IfModule>
# END Block Meta Crawler add-to-cart
La regla es precisa: bloquea al crawler de Facebook solo cuando la URL contiene add-to-cart. Puede seguir indexando productos, imágenes, Open Graph y todo lo necesario para Facebook Shop. Solo no puede “comprar”.
Además, aplicamos dos cambios en wp-config.php para cortar el loopback:
// Desactiva WP-Cron por HTTP (el cron del sistema lo maneja por CLI)
define('DISABLE_WP_CRON', true);
// Desactiva el runner async de Action Scheduler
// El cron CLI ya procesa la cola, no necesita hacer requests HTTP
define('ACTION_SCHEDULER_ALLOW_ASYNC_REQUEST_RUNNER', false);
Y limpiamos la base de datos del Action Scheduler:
-- Eliminar acciones duplicadas del plugin de facturas
DELETE FROM wp_actionscheduler_actions
WHERE hook='update_empty_invoice_number_count'
AND status='pending';
-- Limpiar acciones completadas de más de 7 días
DELETE FROM wp_actionscheduler_actions
WHERE status='complete'
AND last_attempt_gmt < DATE_SUB(NOW(), INTERVAL 7 DAY);
-- Limpiar acciones fallidas de más de 30 días
DELETE FROM wp_actionscheduler_actions
WHERE status='failed'
AND last_attempt_gmt < DATE_SUB(NOW(), INTERVAL 30 DAY);
El resultado fue inmediato

A las 15:31, en el momento en que guardamos los cambios, la gráfica de CPU en Hetzner cayó en picada:
| Métrica | Antes | Después |
|---|---|---|
| Load average | 66 (pico) / 34 (sostenido) | 0.49 |
| Procesos PHP | 78 simultáneos | ~10 |
| Uso de CPU | 1500% (15 de 16 cores) | ~50% (medio core) |
| Tiempo de carga | Timeout | Normal |
El 94% de la capacidad del servidor se estaba yendo en atender un bot y en procesos internos que ese bot generaba. Los clientes reales recibían el 6% restante.

Esto no es exclusivo de Facebook
Si usas PixelYourSite, Facebook Catalog o cualquier integración que publique productos en Facebook o Instagram Shop, el crawler de Meta rastrea tu sitio para indexar lo que vendes. WooCommerce expone URLs con ?add-to-cart=12345 en enlaces internos y redirecciones. Cuando el crawler las sigue, no distingue entre “ver un producto” y “agregarlo al carrito”. Para él es otra URL más.
No es un bug de Facebook ni de WooCommerce. Es cómo interactúan los dos sistemas, y puede pasar con cualquier crawler agresivo. En los logs del mismo servidor vimos peticiones similares de Baiduspider. Google tiende a ser más conservador, pero SEMrush y Ahrefs también pueden caer en lo mismo.
Para cubrir más bots, se puede ampliar la regla:
# BEGIN Block ALL bots from add-to-cart URLs
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{HTTP_USER_AGENT} (bot|crawler|spider|meta-externalagent|Baiduspider|SEMrush|AhrefsBot) [NC]
RewriteCond %{QUERY_STRING} add-to-cart [NC]
RewriteRule .* - [F,L]
</IfModule>
# END Block ALL bots from add-to-cart URLs
Una nota sobre las IPs: en nuestro caso eran todas del rango 57.141.20.x, que pertenece a Meta Platforms. No conviene bloquear el rango completo porque eso impediría que Facebook indexe tus productos. La solución correcta es bloquear solo los requests que incluyen add-to-cart.
Cómo saber si te pasa lo mismo
Si tienes WooCommerce con cualquier integración a Facebook, vale la pena revisar:
Buscar el crawler en tus access logs:
grep 'meta-externalagent' /path/to/access.log | grep 'add-to-cart' | wc -l
Si el número es mayor que cero, te está pasando.
Ver qué IP genera más tráfico:
awk '{print $1}' /path/to/access.log | sort | uniq -c | sort -rn | head -10
Si la IP de tu propio servidor está en los primeros lugares, tienes un problema de loopback.
Ver qué URLs reciben más peticiones:
awk '{print $7}' /path/to/access.log | sort | uniq -c | sort -rn | head -10
Si /wp-admin/admin-post.php o /wp-admin/admin-ajax.php dominan la lista, algo interno está generando carga.
Revisar el Action Scheduler:
wp db query "SELECT status, COUNT(*) total FROM wp_actionscheduler_actions GROUP BY status;"
Miles de acciones complete acumuladas o decenas de pending del mismo hook son señal de que hay que limpiar.
Lo que aprendimos
Perdimos tiempo buscando malware y mineros. El servidor estaba limpio desde el principio. El problema no era de seguridad sino de rendimiento, y la pista estaba en los access logs, no en htop ni en ps aux. Si hubiéramos revisado los logs con User-Agent desde el inicio, lo habríamos resuelto en minutos.
También aprendimos que tener un buen sistema de caché no alcanza. LiteSpeed Cache funciona perfecto para tráfico normal, pero cualquier request que dispare una acción de WooCommerce lo salta. Un bot que hace eso masivamente tiene el mismo efecto que miles de clientes comprando al mismo tiempo.
Otro punto que nos quedó claro: WordPress tiene la costumbre de hacerse peticiones HTTP a sí mismo para procesar tareas en segundo plano. Si ya tienes cron por línea de comandos, ese mecanismo HTTP es redundante. En nuestro caso, generó más de 9,000 requests internos que solo empeoraron la situación.
Y la limpieza del Action Scheduler no es opcional. Plugins que usan colas internas pueden acumular tareas duplicadas durante meses sin que nadie lo note, hasta que se cruza un umbral y todo se viene abajo.
Checklist para servidores WooCommerce en producción
- Bloquear crawlers en URLs con
add-to-cartvía.htaccess - Configurar
DISABLE_WP_CRONenwp-config.phpy usar cron del sistema por CLI - Configurar
ACTION_SCHEDULER_ALLOW_ASYNC_REQUEST_RUNNERcomofalse - Limpiar el Action Scheduler periódicamente (completadas mayores a 7 días)
- Ajustar PHP workers según los cores del servidor (no más de 1 worker por core)
- Revisar access logs buscando bots agresivos
- Verificar que ningún plugin acumule tareas duplicadas en Action Scheduler
Diagnóstico realizado el 31 de marzo de 2026 en un servidor Hetzner CPX62 con RunCloud, OpenLiteSpeed y WordPress 6.9.4. Los sitios afectados son tiendas WooCommerce con más de 50 plugins activos cada una.

