Si alguna vez has visto una publicación «programada» de WordPress pasar su hora de publicación como si hubiera fallado a una reunión, ya conoces a WP-Cron en su hábitat natural: «mejor esfuerzo». A veces funciona durante meses. Luego el tráfico baja, cambia el comportamiento de una caché o PHP-FPM se pone quisquilloso—y de repente tu calendario de contenidos es una mentira.
Esto no es un problema místico de WordPress. Es un sistema de programación construido sobre peticiones web, que corre dentro de una pila que adora optimizar justo lo que necesita: un disparador real y fiable. Volvamos a hacerlo aburrido.
WP-Cron no es cron (y ese es todo el problema)
El cron de Linux es un programador. Se ejecuta independientemente de si tu sitio recibe tráfico. WP-Cron es un programador a nivel de aplicación que se ejecuta porque tu sitio recibe tráfico. Esa única diferencia explica la mayoría de incidentes de «programación perdida».
WP-Cron se dispara cuando WordPress recibe una petición y detecta que hay tareas programadas pendientes. Entonces hace una petición HTTP de «bucle» a wp-cron.php (o la ejecuta en línea, según la configuración y el tiempo de ejecución). En otras palabras, WordPress programa trabajo, pero no posee el reloj. Tus visitantes lo hacen.
Cuando el tráfico es estable y la pila es permisiva, WP-Cron está «bien». Cuando el tráfico es bajo, o las peticiones están en caché, o los loopbacks están bloqueados, WP-Cron no se dispara. Las publicaciones programadas no se publican. Las suscripciones de WooCommerce no se renuevan. Los escaneos de seguridad no se ejecutan. Y aprendes de qué depende realmente tu negocio.
También hay un olor a falta de fiabilidad aquí: WP-Cron vive en PHP, detrás de servidores web, cachés, WAF y timeouts. Si tu ejecutor de cron está acoplado al mismo camino que sirve a los usuarios, has construido un programador que puede ser interrumpido por… servir a los usuarios.
Verdad seca: si la publicación programada importa, quieres un programador real (cron del sistema o un job runner) invocando WordPress con una cadencia conocida. WP-Cron es un recurso de respaldo. Trátalo como tal.
Broma #1: WP-Cron es como un temporizador de cocina que solo suena si alguien abre la nevera.
Cómo falla en producción: síntomas que realmente ves
Las fallas raramente aparecen como una alerta clara de «cron roto». Aparecen como rarezas visibles para el negocio. El truco es mapear síntomas a modos de fallo rápidamente.
Síntomas clásicos
- Publicaciones programadas atascadas en “Programado” o “Missed schedule”. La hora de publicación pasa; no ocurre nada.
- La cola de “Scheduled Actions” de WooCommerce crece. Suscripciones, correos por carritos abandonados, sincronizaciones de stock, webhooks—retrasados en silencio.
- Las copias de seguridad no se ejecutan. Los plugins que dependen de WP-Cron no consiguen tiempo de CPU.
- Actualizaciones de índices de búsqueda / sitemap retrasadas. Las herramientas de SEO dependen de tareas programadas; obtienes resultados obsoletos.
- Ráfagas aleatorias de trabajo en segundo plano. Un pico repentino de tráfico dispara una cola, provocando peticiones lentas y timeouts.
Qué suele ocurrir bajo el capó
- La petición de loopback está bloqueada o rota. WordPress no puede llamarse a sí mismo por DNS, firewall, WAF, autenticación, TLS o redirecciones.
- Las peticiones nunca llegan a WordPress. La caché de página completa sirve las respuestas sin tocar PHP, por lo que WP-Cron nunca recibe el «tic».
- Agotamiento de workers PHP. Incluso si se dispara, cron no puede ejecutarse porque los workers de FPM están ocupados.
- Timeouts. Cron comienza, se mata, deja un lock/transient y nada se ejecuta hasta que el bloqueo expira.
- Problemas de reloj. Deriva de tiempo o ajustes de zona horaria desajustados confunden la programación.
Guía rápida de diagnóstico
Este es el orden en que reviso las cosas cuando el negocio dice «las publicaciones programadas no se publicaron» y quiero una respuesta antes del siguiente standup.
Primero: ¿WordPress está intentando programar eventos?
- Lista los eventos programados con WP-CLI.
- Si la cola está vacía, el problema puede ser la lógica de programación del plugin/tema—o confusión de zona horaria.
- Si la cola está llena de eventos vencidos, el ejecutor no está ejecutando.
Segundo: ¿puede el sitio hacer loopback a sí mismo?
- Desde el servidor, haz curl a
wp-cron.phpy revisa el código HTTP y la latencia. - Desde WordPress mismo, las pruebas de loopback pueden fallar por DNS o egress bloqueado.
- Revisa redirecciones a login, forzado de HTTPS o desafíos del WAF.
Tercero: ¿está PHP siquiera disponible para ejecutarlo?
- Revisa el estado de PHP-FPM: max children alcanzados, logs de lentitud, backlog.
- Revisa los logs de errores del servidor web por timeouts upstream y picos de 502/504.
- Confirma que OPcache y autoload están sanos (cron a menudo ejecuta rutas de código raramente tocadas).
Después: elimina el “tráfico” de la ecuación
- Deshabilita los triggers de WP-Cron y ejecuta el cron de WordPress vía cron del sistema cada minuto o cada cinco minutos.
- Revisa los eventos vencidos; deberían vaciarse de forma predecible.
Esta guía está sesgada a encontrar el cuello de botella rápido. No es elegante. Funciona.
Tareas prácticas: comandos, salidas y decisiones (12+)
Estos son comandos ejecutables para un host Linux típico que corre Nginx/Apache + PHP-FPM y WordPress. Ajusta rutas y usuarios, pero conserva la intención. Cada tarea incluye: el comando, salida de ejemplo, qué significa y la decisión que tomas.
Tarea 1: Confirma la hora y la zona horaria del sitio a nivel OS
cr0x@server:~$ timedatectl
Local time: Sat 2025-12-27 14:31:19 UTC
Universal time: Sat 2025-12-27 14:31:19 UTC
RTC time: Sat 2025-12-27 14:31:18
Time zone: Etc/UTC (UTC, +0000)
System clock synchronized: yes
NTP service: active
RTC in local TZ: no
Qué significa: El reloj del SO está en UTC y sincronizado. Bien. Si esto muestra System clock synchronized: no o una zona horaria extraña, las publicaciones programadas pueden desviarse o parecer “perdidas”.
Decisión: Si NTP no está activo/sincronizado, arregla la hora primero. Depurar cron con un reloj que deriva es como perseguir un blanco en movimiento.
Tarea 2: Revisa la zona horaria y la hora actual de WordPress vía WP-CLI
cr0x@server:~$ cd /var/www/html
cr0x@server:/var/www/html$ wp option get timezone_string
America/New_York
cr0x@server:/var/www/html$ wp eval 'echo "wp_time: ".wp_date("c").PHP_EOL;'
wp_time: 2025-12-27T09:31:33-05:00
Qué significa: WordPress usa una configuración de zona horaria del sitio. Si está en blanco, WordPress puede depender de un offset UTC y el comportamiento con DST puede volverse extraño.
Decisión: Prefiere una zona horaria nombrada (como America/New_York) sobre un offset bruto. Es menos sorprendente alrededor del cambio de horario.
Tarea 3: ¿Se está comunicando WP-CLI con el WordPress correcto?
cr0x@server:/var/www/html$ wp core version
6.7.1
cr0x@server:/var/www/html$ wp site list
+----+---------------------+----------------------------+
| id | url | last_updated |
+----+---------------------+----------------------------+
| 1 | https://example.com | 2025-12-27 14:29:05 +0000 |
+----+---------------------+----------------------------+
Qué significa: Estás operando sobre la instalación prevista y (si es multisite) la red correcta.
Decisión: Si esto da error, arregla permisos o problemas de --path antes de continuar. El depurado a ciegas es cómo «arreglas» staging por accidente.
Tarea 4: Lista eventos cron y encuentra los vencidos
cr0x@server:/var/www/html$ wp cron event list --fields=hook,next_run,recurrence --format=table | head
+-------------------------------+---------------------+------------+
| hook | next_run | recurrence |
+-------------------------------+---------------------+------------+
| wp_version_check | 2025-12-27 13:55:00 | twice_daily|
| wp_scheduled_delete | 2025-12-27 14:00:00 | daily |
| action_scheduler_run_queue | 2025-12-27 14:01:00 | every_minute|
| wp_update_plugins | 2025-12-27 12:10:00 | twice_daily|
+-------------------------------+---------------------+------------+
Qué significa: Si next_run está en el pasado por minutos/horas y no se ejecuta, el ejecutor de cron está roto o bloqueado.
Decisión: Si los eventos están vencidos, pasa a las comprobaciones de loopback y del ejecutor. Si no hay eventos programados, investiga plugins/temas o la lógica de programación.
Tarea 5: Ejecuta cron ahora, manualmente, y observa qué ocurre
cr0x@server:/var/www/html$ wp cron event run --due-now
Success: Executed a total of 12 cron events.
Qué significa: WordPress puede ejecutar eventos cuando se le pide. Eso sugiere que el problema es el disparador/ejecutor, no los manejadores de eventos en sí.
Decisión: Si la ejecución manual funciona, implementa cron del sistema y deshabilita los triggers de WP-Cron. Si la ejecución manual falla con fatales, ve a los logs de PHP y a la aislamiento de plugins.
Tarea 6: Comprueba si DISABLE_WP_CRON está definido
cr0x@server:/var/www/html$ grep -n "DISABLE_WP_CRON" wp-config.php
91:define('DISABLE_WP_CRON', true);
Qué significa: WP-Cron está deshabilitado. Eso está bien solo si tienes un job de cron del sistema llamándolo.
Decisión: Si está en true y no hay cron del sistema configurado, encontraste el bug. Añade un job de cron del sistema (ver soluciones).
Tarea 7: Revisa el cron del sistema por un ejecutor de WordPress
cr0x@server:~$ sudo crontab -l
no crontab for root
Qué significa: Root no tiene cron. El ejecutor puede existir bajo el usuario web o en /etc/cron.d.
Decisión: Revisa el usuario correspondiente y los directorios de cron del sistema.
cr0x@server:~$ crontab -l
# m h dom mon dow command
*/5 * * * * cd /var/www/html && wp cron event run --due-now --quiet
Qué significa: Hay un ejecutor, cada 5 minutos, usando WP-CLI. Eso es una línea base sensata.
Decisión: Si las publicaciones programadas aún faltan, el ejecutor puede no ejecutarse (permisos, PATH, PHP), o los trabajos están fallando.
Tarea 8: Verifica que cron realmente se ejecuta (inspecciona syslog/journal)
cr0x@server:~$ sudo journalctl -u cron --since "1 hour ago" | tail -n 10
Dec 27 14:25:01 server CRON[21901]: (cr0x) CMD (cd /var/www/html && wp cron event run --due-now --quiet)
Dec 27 14:30:01 server CRON[22188]: (cr0x) CMD (cd /var/www/html && wp cron event run --due-now --quiet)
Qué significa: El demonio cron está ejecutando el job según lo programado.
Decisión: Si no ves entradas, cron puede estar detenido, enmascarado, o los logs van a otro lado. Arregla la plataforma primero.
Tarea 9: Confirma que el comando wp-cli funciona en el entorno de cron
cr0x@server:~$ env -i HOME=/tmp PATH=/usr/bin:/bin bash -lc 'cd /var/www/html && wp cron event run --due-now'
Success: Executed a total of 0 cron events.
Qué significa: Incluso con un entorno mínimo (similar al de cron), WP-CLI puede ejecutarse. «0 events» puede estar bien si no hay nada debido.
Decisión: Si esto falla, codifica rutas en crontab y asegúrate de que WP-CLI esté instalado donde cron pueda encontrarlo.
Tarea 10: Prueba acceso de loopback a wp-cron.php desde el servidor
cr0x@server:~$ curl -I -sS https://example.com/wp-cron.php?doing_wp_cron=1 | head -n 5
HTTP/2 200
content-type: text/html; charset=UTF-8
cache-control: no-cache, must-revalidate, max-age=0
date: Sat, 27 Dec 2025 14:32:10 GMT
Qué significa: El endpoint es accesible y devuelve 200. Bien. Si ves bucles 301/302, 403, 503 o una página de desafío del WAF, los triggers de WP-Cron pueden fallar.
Decisión: Si el loopback falla, arregla DNS/TLS/redirecciones/reglas WAF o evita el loopback de WP-Cron usando cron del sistema con WP-CLI.
Tarea 11: Comprueba si la caché de página completa impide hits a PHP
cr0x@server:~$ curl -sS -I https://example.com/ | egrep -i 'x-cache|cf-cache-status|age|server|via'
server: nginx
x-cache: HIT
age: 1842
Qué significa: Las respuestas pueden servirse sin tocar PHP. Si tu sitio tiene poco tráfico y está mayoritariamente en caché, WP-Cron puede casi no dispararse.
Decisión: No dependas de los visitantes para impulsar la programación. Usa cron del sistema. Opcionalmente, excluye /wp-cron.php de la caché y del rate limiting.
Tarea 12: Revisa la presión sobre PHP-FPM (agotamiento de workers)
cr0x@server:~$ sudo tail -n 20 /var/log/php8.2-fpm.log
[27-Dec-2025 14:20:55] WARNING: [pool www] server reached pm.max_children setting (20), consider raising it
[27-Dec-2025 14:20:56] WARNING: [pool www] child 18734, script '/var/www/html/wp-cron.php' (request: "GET /wp-cron.php?doing_wp_cron=...") executing too slow (12.345 sec), logging
Qué significa: Cron está intentando ejecutarse, pero PHP está saturado o lento. Las peticiones de WP-Cron pueden hacer timeout o nunca iniciarse.
Decisión: Incrementa capacidad (pm.max_children), reduce la carga de peticiones, mueve cron a un pool separado o descarga trabajos pesados.
Tarea 13: Busca errores fatales de WordPress durante cron
cr0x@server:~$ sudo tail -n 30 /var/log/nginx/error.log
2025/12/27 14:21:03 [error] 1229#1229: *9981 FastCGI sent in stderr: "PHP Fatal error: Uncaught Error: Call to undefined function mb_strlen() in /var/www/html/wp-includes/formatting.php:..."
2025/12/27 14:21:03 [error] 1229#1229: *9981 upstream prematurely closed FastCGI stdout while reading response header from upstream, client: 127.0.0.1, server: example.com, request: "GET /wp-cron.php?doing_wp_cron=..."
Qué significa: Cron murió por un fatal de PHP (extensión faltante en este ejemplo). Eso puede dejar eventos vencidos indefinidamente.
Decisión: Arregla el runtime de PHP subyacente. Cron no es especial; golpea rutas de código que las vistas de página normales quizá no ejerciten.
Tarea 14: Identifica el hook que se está acumulando (Action Scheduler)
cr0x@server:/var/www/html$ wp action-scheduler list --status=pending --per-page=5
+----------+------------------------------+---------------------+----------+
| hook | args | scheduled_date_gmt | status |
+----------+------------------------------+---------------------+----------+
| wc_email | {"id":12345,"type":"follow"} | 2025-12-27 13:58:00 | pending |
| wc_email | {"id":12346,"type":"follow"} | 2025-12-27 13:59:00 | pending |
| wc_email | {"id":12347,"type":"follow"} | 2025-12-27 14:00:00 | pending |
+----------+------------------------------+---------------------+----------+
Qué significa: El Action Scheduler de WooCommerce tiene un backlog. Esto puede ser «cron no se ejecuta» o «cron se ejecuta pero no da abasto».
Decisión: Ejecuta la cola manualmente para probar, luego escala el ejecutor o arregla el rendimiento.
Tarea 15: Ejecuta la cola de Action Scheduler manualmente (prueba controlada)
cr0x@server:/var/www/html$ wp action-scheduler run --batch-size=25
Processed 25 actions.
Qué significa: Los trabajos pueden ejecutarse. Si esto es lento o falla, tienes una carga/rendimiento o bug de plugin, no solo un problema de disparador.
Decisión: Si las ejecuciones manuales funcionan, programa un ejecutor adecuado. Si fallan, aisla el plugin/hook y soluciona la causa raíz.
Tarea 16: Verifica que la base de datos no sea el cuello de botella oculto (locks, consultas lentas)
cr0x@server:~$ sudo mysql -e "SHOW PROCESSLIST;" | head
Id User Host db Command Time State Info
42 wpuser localhost wordpress Query 12 Waiting for table metadata lock UPDATE wp_options SET option_value='...' WHERE option_name='cron'
77 wpuser localhost wordpress Query 2 Sending data SELECT * FROM wp_actionscheduler_actions WHERE status='pending' LIMIT 0, 25
Qué significa: Las actualizaciones de cron (almacenadas en wp_options) pueden verse bloqueadas por metadata locks. Cambios pesados de esquema, transacciones largas o consultas malas pueden detener la programación.
Decisión: Si ves esperas por locks, encuentra el bloqueador, reduce transacciones largas y evita ejecutar DDL en hora pico. Cron depende de escrituras rápidas en options.
Soluciones que funcionan: desde parches rápidos hasta programación real
Solución 1: Deja de depender del tráfico — usa cron del sistema + WP-CLI
Esta es la solución de adultos. Si estás en un VPS, metal desnudo o cualquier host donde controles cron, hazlo.
- Deshabilita el trigger de WP-Cron añadiendo esto a
wp-config.php:cr0x@server:~$ sed -n '1,120p' /var/www/html/wp-config.php | tail -n 5 define('DB_COLLATE', ''); define('DISABLE_WP_CRON', true); /* That's all, stop editing! Happy publishing. */ - Añade una entrada en crontab (cada 1–5 minutos según carga):
cr0x@server:~$ crontab -e */1 * * * * cd /var/www/html && /usr/local/bin/wp cron event run --due-now --quiet
¿Por qué WP-CLI en vez de golpear wp-cron.php? Menos piezas móviles. No hay WAF. No hay TLS. No hay redirecciones. No hay caché. Solo PHP ejecutando WordPress en un contexto controlado.
Compromiso: Debes mantener WP-CLI instalado y accesible. No es difícil. Es un archivo.
Solución 2: Si no puedes ejecutar WP-CLI, llama a wp-cron.php desde cron igualmente
A veces estás en un hosting compartido restringido o en una plataforma donde falta WP-CLI. Aún puedes usar cron del sistema para llamar al endpoint, pero vuelves a la fragilidad HTTP.
cr0x@server:~$ crontab -e
*/2 * * * * curl -sS -o /dev/null https://example.com/wp-cron.php?doing_wp_cron=1
Decisión: Usa esto solo si es imprescindible. Si falla de forma intermitente por WAF/rate-limits, reintroducirá silenciosamente el problema.
Solución 3: Arregla fallos de loopback (403/401/redirecciones/WAF)
Los fallos de loopback son los más molestos porque el sitio funciona para humanos. Solo falla cuando el sitio se llama a sí mismo.
- Basic auth en staging o producción: El loopback recibe 401. Arregla permitiendo IPs de loopback o configura cron para usar WP-CLI.
- Forzado de HTTPS / bucles de redirección:
siteurl/homeincorrectos o cabeceras proxy mal puestas. Corrige la configuración del reverse proxy y las opciones de URL de WordPress. - WAF/Protección contra bots: Los desafíos bloquean peticiones no-browser. Excluye
/wp-cron.phpo deja de usar loopback HTTP por completo.
Solución 4: Excluye wp-cron.php de la caché y de rate limiting agresivo
Incluso si ejecutas cron vía cron del sistema, puede que haya plugins que aún disparen loopbacks. Haz los endpoints de cron «aburridos».
Como mínimo, no cachees /wp-cron.php y evita aplicar desafíos de bot a ese endpoint. Si tu CDN insiste en ser «útil», al final te ayudará a perder una programación.
Solución 5: Separa la capacidad de PHP para cron (sitios serios hacen esto)
Si tu front-end de WordPress está ocupado, cron compite por los mismos workers. Bajo carga, cron se queda sin recursos. O peor: cron inicia y hace que el front-end se vuelva más lento. Elige tu veneno.
Opciones:
- Pool PHP-FPM dedicado para rutas de admin y cron, con sus propios límites.
- Host/contenedor ejecutor separado que llame a WP-CLI contra la misma base de código y base de datos.
- Encolar tareas pesadas fuera de WordPress por completo (envío de emails, exportes, feeds) y dejar que WP solo encole.
Solución 6: Maneja el “lock” de cron y transients atascados
WordPress usa un mecanismo de bloqueo (guardado en options/transients) para evitar ejecuciones concurrentes de cron. Si un proceso muere a mitad de ejecución, puedes obtener el patrón de «cron bloqueado» donde nada se ejecuta hasta que expire una ventana de timeout.
Prefiere diagnosticar por qué cron está muriendo (fatales, memoria, timeouts), pero cuando necesitas desatascar producción:
cr0x@server:/var/www/html$ wp option list --search=cron --fields=option_name,autoload --format=table | head
+----------------+----------+
| option_name | autoload |
+----------------+----------+
| cron | yes |
+----------------+----------+
Decisión: No elimines la opción cron; esa es la tienda de programación. Si sospechas de un bloqueo obsoleto, aborda el transient doing_cron si está presente, pero solo tras confirmar que no hay una ejecución activa.
Solución 7: Haz la publicación programada resistente (no apuestes el negocio a un solo mecanismo)
Si publicar a tiempo es crítico para ingresos (anuncios, campañas, avisos legales), considera cinturón y tirantes:
- Cron del sistema que ejecute cada minuto.
- Un monitor externo que compruebe publicaciones vencidas y alerte a SRE/ops si vuelve a ocurrir.
- Los editores reciben advertencias en la interfaz para horarios fallidos (no es una solución, pero es una señal más rápida).
Parafraseando una idea: Werner Vogels ha insistido en que «todo falla, todo el tiempo», así que los sistemas deben diseñarse para esperar y tolerar fallos.
Broma #2: Si tu plan de respuesta a incidentes es «refrescar la página hasta que publique», enhorabuena—has inventado el cron impulsado por humanos.
Errores comunes: síntoma → causa raíz → solución
Esta es la parte donde dejamos de ser amables con nuestros antiguos yo.
1) Las publicaciones programadas nunca se publican en sitios de bajo tráfico
Síntoma: Las publicaciones programadas por la noche no se publican hasta que alguien visita el sitio.
Causa raíz: WP-Cron depende de cargas de página para dispararse.
Solución: Deshabilita los triggers de WP-Cron y usa cron del sistema con WP-CLI cada 1–5 minutos.
2) Picos de “Missed schedule” tras activar una nueva caché/CDN
Síntoma: Todo iba bien; luego se activó la caché y los eventos programados se detuvieron.
Causa raíz: La caché sirve páginas sin PHP, quitando el «tic» de WP-Cron. A veces incluso /wp-cron.php se cachea o se limita por rate limiting.
Solución: Usa cron del sistema. Excluye /wp-cron.php de la caché y de los desafíos del WAF.
3) wp-cron.php devuelve 403 solo desde el servidor
Síntoma: Los navegadores reciben 200; el loopback desde el servidor recibe 403 o una página de desafío.
Causa raíz: Las reglas del WAF tratan las peticiones originadas por el servidor como bots; o la IP de egress difiere; o las cabeceras no coinciden con lo esperado.
Solución: Añade en whitelist la IP de egress del servidor para /wp-cron.php, o deja de usar loopback y ejecuta WP-CLI desde cron.
4) Cron se ejecuta, pero la cola nunca se vacía
Síntoma: Los eventos aparecen vencidos, la ejecución manual procesa algunos, pero la pila permanece.
Causa raíz: La carga es demasiado alta para la cadencia/capacidad; Action Scheduler necesita más rendimiento; los timeouts de PHP matan las ejecuciones a mitad.
Solución: Aumenta la frecuencia del ejecutor, aumenta tamaños de lote con cautela, añade capacidad PHP y analiza hooks lentos. Considera un pool de workers separado.
5) DISABLE_WP_CRON configurado sin un ejecutor real
Síntoma: Todo lo programado se detiene tras un cambio de «tuning» de rendimiento.
Causa raíz: Alguien deshabilitó WP-Cron para «reducir peticiones» y olvidó el reemplazo por cron del sistema.
Solución: Añade cron del sistema con WP-CLI; verifica en logs que se ejecute.
6) Loopback falla por URLs del sitio incorrectas después de una migración
Síntoma: El endpoint de cron redirige repetidamente o apunta al dominio equivocado.
Causa raíz: home/siteurl desajustados, o cabeceras del reverse proxy mal puestas, causando que WordPress genere URLs internas erróneas.
Solución: Corrige las opciones de URL, arregla las cabeceras del proxy y vuelve a probar curl a wp-cron.php.
7) Cron falla solo durante picos de tráfico
Síntoma: La noche está bien; lanzamientos y ventas causan programaciones perdidas.
Causa raíz: Saturación de PHP-FPM; las peticiones de cron se encolan o se matan.
Solución: Separa cron en su propio pool/host, aumenta la capacidad de FPM, reduce plugins costosos y mantén limitada la carga de cron.
8) Fallos “aleatorios” rastreados hasta DNS
Síntoma: El loopback a wp-cron.php falla de forma intermitente con timeouts.
Causa raíz: El servidor resuelve el dominio público hacia una IP externa, hace hairpin a través de un firewall/CDN, o choca con problemas IPv6; a veces el DNS interno difiere del público.
Solución: Usa un ejecutor WP-CLI. Si debes usar loopback, arregla la resolución DNS y prefiere patrones de invocación directos a localhost.
Tres micro-historias corporativas desde el país de “debería haber funcionado”
Micro-historia #1: La suposición equivocada (“Cron corre porque existe tráfico”)
El equipo de marketing de una SaaS mediana movió su blog de WordPress detrás de una CDN nueva y brillante. La velocidad de página mejoró. Las puntuaciones de Lighthouse dieron para presumir. Todos ganaron.
Dos semanas después, una publicación programada anunciando un webinar no se publicó. Alguien lo notó una hora después del inicio. La teoría inmediata fue error editorial: «Se olvidaron de publicar». La segunda teoría fue la zona horaria: «¿Quizá está en UTC?» Ruleta clásica de culpas.
En el servidor, la cola de cron de WordPress mostraba eventos vencidos. Ejecutar manualmente wp cron event run --due-now publicó la entrada al instante. Esa fue la pista: el sistema podía ejecutar; simplemente no se estaba disparando.
La suposición equivocada era sutil: «Tenemos tráfico, por lo tanto WP-Cron se ejecutará.» Pero la CDN servía la mayoría de páginas desde caché. PHP no se estaba tocando. Los visitantes eran reales; sus peticiones nunca llegaron a WordPress. WP-Cron había sido efectivamente desconectado.
La solución fue simple y algo embarazosa: deshabilitar los triggers de WP-Cron, instalar WP-CLI y ejecutar cron del sistema cada minuto. Después de eso, publicar volvió a ser una función del tiempo, no de vistas de página.
Micro-historia #2: La optimización que salió mal (“Desactivar wp-cron.php por rendimiento”)
Un equipo de ops heredó un clúster WordPress con picos de latencia ocasionales. Alguien encontró posts que decían que wp-cron.php es «lento» y decidió optimizar. Bloquearon el acceso a /wp-cron.php en el borde y pusieron DISABLE_WP_CRON en wp-config.php.
En aislamiento, no es una locura. Pero no añadieron un reemplazo por cron del sistema. El sitio parecía bien porque el front-end seguía sirviendo contenido. La primera falla visible llegó días después: las suscripciones de WooCommerce no se renovaron a tiempo y se acumuló una pila de acciones «pending».
Cuando finalmente investigaron, encontraron un backlog lo bastante grande como para convertir «habilitar cron» en un mini DDoS autoinducido. La primera ejecución tras reactivar intentó ejecutar horas de tareas atrasadas bajo carga pico. Los workers de PHP-FPM alcanzaron sus límites. Los clientes recibieron 502s. La solución provocó un segundo incidente.
La resolución no fue solo «activar cron». Introdujeron un ejecutor controlado: WP-CLI cron cada minuto, cadencia del Action Scheduler ajustada y un tope en tamaños de lote. También aprendieron a tratar los cambios de «deshabilitar» como cambios de producción con planes de rollback.
La optimización es genial. La optimización sin plan de reemplazo es solo un incidente más lento.
Micro-historia #3: La práctica aburrida que salvó el día (ejecutor separado + monitorización)
Un editor con múltiples sitios WordPress ya había sufrido antes. No confiaba en WP-Cron, así que construyó una configuración simple y efectiva: cada sitio tenía DISABLE_WP_CRON activado y un job de cron del sistema ejecutando WP-CLI. Registraban tiempos de ejecución y contaban eventos vencidos como métrica.
Una mañana, un problema de almacenamiento en una VM causó picos de latencia de I/O. El front-end se mantuvo gracias a la caché, pero los procesos PHP se ralentizaron. El job de cron todavía se ejecutó, pero empezó a tardar más de lo esperado y la métrica de «eventos vencidos» comenzó a subir.
Nadie esperó a que los editores se quejaran. La alerta saltó cuando los eventos vencidos superaron un umbral durante varios minutos. El ingeniero on-call revisó logs, vio advertencias de lentitud de PHP-FPM y lo correlacionó con la latencia de disco. Cambiaron temporalmente la ejecución de cron a un host ejecutor menos afectado y limitaron las tareas en segundo plano hasta que el almacenamiento se estabilizó.
¿Se publicaron las entradas a tiempo? En su mayor parte sí. Algunas se retrasaron unos minutos, pero nada perdió una hora. El negocio casi no lo notó. Ese es el objetivo: fallos que pasan desapercibidos porque tus controles aburridos los detectaron temprano.
No ganaron por ser ingeniosos. Ganaron por estar preparados.
Hechos interesantes y contexto (por qué existe este diseño)
WP-Cron parece extraño si vienes de sistemas tradicionales. Deja de parecerlo cuando recuerdas los entornos a los que WordPress apuntaba originalmente.
- WP-Cron existe en gran parte porque el hosting compartido no ofrecía cron de forma fiable. WordPress tenía que programar tareas sin asumir privilegios a nivel OS.
- Es «pseudo-cron» por diseño. Se aprovecha de las peticiones de página para aproximar una programación basada en tiempo.
- WordPress guarda los cron schedules en la base de datos (en
wp_options). Eso lo hace portable, pero también lo vuelve sensible a locks de BD y escrituras lentas. - La petición de loopback es a la vez una característica y una responsabilidad. Evita ejecutar trabajo pesado durante la petición del usuario, pero depende de que HTTP funcione internamente.
- Algunos plugins evitan el loopback y ejecutan tareas durante cargas normales de página. Eso puede «funcionar» hasta que cause picos de latencia bajo carga. Fiabilidad frente a rendimiento, el eterno intercambio.
- Action Scheduler de WooCommerce se volvió común porque WP-Cron solo era demasiado basta para cargas pesadas. Añade persistencia, reintentos y visibilidad—pero aún necesita un ejecutor.
- El síntoma «missed schedule» a menudo no es la lógica de programación de la publicación. Es el ejecutor que no se ejecuta en la marca de tiempo de publicación, dejando las publicaciones en limbo hasta el siguiente tic de cron.
- La caché moderna hace que WP-Cron sea menos fiable que en 2008. Las cachés reducen los hits a PHP, y WP-Cron depende de hits a PHP para dispararse.
- Los reverse proxies y la imposición de HTTPS introdujeron nuevos modos de fallo de loopback. Cabeceras incorrectas o ajustes de URL mixtos pueden causar redirecciones internas que solo ve cron.
Listas de verificación / plan paso a paso
Lista A: Triaje inmediato (15 minutos)
- Confirma sincronización de hora del OS (
timedatectl). Si no está sincronizada, arregla NTP primero. - Lista eventos vencidos (
wp cron event list). Si están vencidos, el ejecutor está roto. - Ejecuta manualmente los eventos vencidos (
wp cron event run --due-now) para restaurar el servicio. - Revisa si WP-Cron está deshabilitado (
grep DISABLE_WP_CRON). - Haz curl a
/wp-cron.phpy captura el código HTTP. 200 es bueno; cualquier otra cosa es una pista. - Mira los logs de PHP-FPM y errores web por timeouts/fatales alrededor de los intentos de cron.
Lista B: Solución permanente para la mayoría de sitios (60–90 minutos)
- Instala WP-CLI en una ubicación estable (gestor de paquetes o phar verificado), asegúrate de que corre como el usuario correcto.
- Configura
define('DISABLE_WP_CRON', true);enwp-config.php. - Crea un job de cron del sistema que ejecute WP-CLI cada minuto o cinco minutos.
- Verifica la ejecución vía
journalctl -u crony confirma que los eventos vencidos se drenen. - Si existe WooCommerce/Action Scheduler, valida la salud de la cola (
wp action-scheduler list). - Igualmente, excluye
/wp-cron.phpde caché/WAF. Otros plugins pueden seguir llamándolo.
Lista C: Endurecimiento para sitios de alta carga o críticos para ingresos
- Separa la ejecución de cron (pool PHP-FPM dedicado o host ejecutor separado).
- Mide eventos vencidos y profundidad de cola; alerta cuando se excedan umbrales.
- Acota tiempo de ejecución: ajusta tamaños de lote, evita patrones de «ejecutar todo» que causen picos.
- Audita tareas programadas: elimina trabajos redundantes, reduce frecuencia y evita trabajo pesado dentro de peticiones de página.
- Planifica para el fallo: un plan de replay de backlog que no vaya a aplastar tu front-end.
Preguntas frecuentes
1) ¿Por qué fallan las publicaciones programadas cuando el sitio no tiene visitantes?
Porque WP-Cron se dispara con peticiones al sitio. Sin peticiones, no hay disparador. Usa cron del sistema (o un job runner) para invocar WordPress en una programación.
2) ¿Debería deshabilitar WP-Cron?
Sí—si lo reemplazas por cron del sistema. Deshabilitarlo sin reemplazo es una forma segura de romper publicaciones programadas y tareas en segundo plano.
3) ¿Llamar a wp-cron.php desde cron del sistema es suficiente?
Puedes hacerlo, pero es frágil (HTTP, WAF, TLS, redirecciones). WP-CLI es más fiable porque evita todo el camino web.
4) Mi wp-cron.php devuelve 200. ¿Por qué siguen habiendo tareas vencidas?
200 solo significa que el endpoint responde. Cron aún puede fallar por fatales de PHP, timeouts, locks de DB o agotamiento de workers. Revisa logs y ejecuta wp cron event run --due-now para ver si la ejecución funciona.
5) ¿La caché rompe WP-Cron?
Con frecuencia, sí de forma indirecta. La caché de página completa reduce los hits a PHP, lo que reduce los disparos de WP-Cron. También puede interferir directamente si /wp-cron.php se cachea o se ratea.
6) ¿Y las “Scheduled Actions” de WooCommerce?
Eso es Action Scheduler. Es más visible que WP-Cron estándar pero aún necesita un ejecutor. Usa WP-CLI para ejecutarlo y asegúrate de que tu cadencia y capacidad de cron puedan drenar la cola.
7) ¿Las zonas horarias pueden causar “missed schedule”?
Sí, especialmente con DST y configuraciones de zona horaria mal configuradas. Confirma la sincronización del SO y luego confirma la zona horaria y la hora actual de WordPress vía WP-CLI.
8) ¿Con qué frecuencia debería correr el cron del sistema?
Opciones comunes: cada minuto para sitios ocupados con mucho trabajo en cola, o cada cinco minutos para sitios más ligeros. La respuesta correcta es: con la frecuencia suficiente para que «vencido» se mantenga cerca de cero.
9) ¿WP-Cron representa un riesgo de seguridad?
No inherentemente, pero exponer wp-cron.php a internet puede atraer tráfico ruidoso. Si usas WP-CLI con cron del sistema, puedes mantenerlo simple y reducir dependencias externas.
10) ¿Y si estoy en hosting gestionado y no puedo usar cron del sistema?
Usa la facility de tareas programadas que ofrezca el host. Muchos tienen «cron jobs» en un panel o una API de scheduler. Si lo único que puedes hacer es HTTP, llama a /wp-cron.php y exímelo de WAF/caché.
Conclusión: siguientes pasos para evitar incidentes recurrentes
Las publicaciones programadas de WordPress fallan por la misma razón que fallan la mayoría de sistemas en producción: supuestos ocultos. El supuesto aquí es que el trabajo basado en tiempo se ejecutará porque existe tráfico web y el HTTP interno se comportará. Eso no es ingeniería; es esperanza con un CMS.
Haz lo práctico:
- Revisa la cola y ejecuta manualmente los eventos vencidos para restaurar el servicio.
- Deja de depender de los triggers de WP-Cron. Usa cron del sistema con WP-CLI.
- Endurece la ruta: excluye
/wp-cron.phpde caché/WAF y separa la capacidad de cron si tienes carga. - Añade un monitor: eventos vencidos o backlog de Action Scheduler. Detecta la próxima falla antes que tus editores.
Haz que la programación sea aburrida. Tu calendario de contenidos merece algo mejor que «alguien visitó la página principal en el momento justo».