Monitorizar cron jobs: cómo saber cuándo una tarea falla en silencio
Respuesta corta: cron ejecuta tu tarea y no le cuenta a nadie qué pasó. Si la tarea falla, se queda colgada o ni siquiera arranca, te enteras cuando te lo dice un cliente, una carpeta de backups vacía o una factura que nunca se emitió. Monitorizar cron jobs le da la vuelta a esto: la tarea llama a una URL al terminar y, si esa llamada no llega a tiempo, recibes una alerta. Esto se llama heartbeat monitoring (monitorización por latido) y basta con una línea en tu crontab.
Datos verificados el 14 de octubre de 2026 con la página de manual de crontab, la documentación de programación de tareas de Laravel y la documentación de GitHub sobre workflows programados. Las fuentes están al final.
Por qué los cron jobs fallan sin avisar
Una página web que deja de funcionar se nota en minutos. Una tarea nocturna que deja de funcionar se nota cuando alguien necesita lo que debía producir. Cuando un cron no se ejecuta, o se ejecuta y falla, las causas habituales son estas:
- Un entorno distinto. Según el manual de crontab, cron solo define
SHELL=/bin/sh,LOGNAMEyHOMEpara tu tarea. Todo lo que carga tu shell de inicio de sesión —entradas extra enPATH, gestores de versiones, variables exportadas— no está. Un script que funciona cuando lo lanzas a mano falla bajo cron. - Un signo de porcentaje. En una línea de crontab, un
%sin escapar se convierte en un salto de línea y todo lo que va detrás se envía al comando como entrada. Undate +%Fen el nombre de un fichero de backup rompe en silencio la línea entera. - Una salida que no llega a ninguna parte. Cron envía por correo la salida de la tarea al propietario del crontab, o a
MAILTO. En la mayoría de los servidores nadie lee ese buzón, o directamente no se entrega ningún correo: el mensaje de error existe, pero nadie lo ve. - Una tarea que nunca termina. Basta una llamada de red sin timeout o un lock que dejó una ejecución anterior que se cayó, y la tarea se queda colgada. Sin error, sin salida, simplemente nada.
- El cambio de horario. El manual de crontab lo dice sin rodeos: las horas que no existen durante el cambio nunca coinciden, así que esas tareas no se ejecutan, y las horas que ocurren dos veces hacen que las tareas se ejecuten dos veces.
- La programación desaparece. Una migración de servidor, un contenedor reconstruido o un deploy nuevo sin el crontab, y la tarea simplemente deja de existir.
- El planificador de la plataforma no es una garantía. La documentación de GitHub indica que los workflows programados pueden retrasarse en periodos de alta carga, que algunos jobs en cola pueden descartarse y que, en un repositorio público, se desactivan automáticamente tras 60 días sin actividad en el repositorio.
Ninguna de estas situaciones genera una alerta por sí sola. Ese es el verdadero problema de cron: un fallo se ve exactamente igual que un éxito, en silencio.
Por qué la monitorización de disponibilidad no lo detecta
La monitorización de disponibilidad (uptime) le hace una pregunta a tu servidor y comprueba la respuesta. Un cron job no responde preguntas: no hay puerto al que conectarse ni página que cargar. El fallo que necesitas detectar es una ausencia: una ejecución que debería haber ocurrido y no ocurrió. Solo algo que espera una señal a una hora concreta puede darse cuenta de que la señal nunca llegó.
Cómo funciona el heartbeat monitoring
Para monitorizar tareas programadas, le dices al servicio de monitorización cuándo debe ejecutarse la tarea —cada 15 minutos, o con una expresión cron como 0 3 * * *— y cuánto retraso es aceptable (el grace period o periodo de gracia). La tarea hace ping a una URL única cada vez que termina correctamente. Con eso, el servicio sabe en qué estado está la tarea:
| Situación | Estado en el panel | Alerta |
|---|---|---|
| El ping llegó a su hora | Correcta | — |
| La hora programada ya pasó, pero sigue dentro del periodo de gracia | Con retraso | — (variación normal) |
| Ningún ping antes de la hora programada más el periodo de gracia | Perdida | Sí |
| La tarea informó de un fallo | Perdida | Sí, de inmediato |
| La tarea informó de su inicio y nunca terminó | Perdida | Sí, tras el periodo de gracia |
| Llega el siguiente ping correcto | Correcta | Aviso de recuperación |
Las dos últimas filas explican por qué una buena configuración envía más de una señal. Un ping al final te dice que la tarea terminó. Un ping también al principio te dice que empezó, así que una tarea que se cuelga a mitad se detecta en lugar de parecer una que nunca se ejecutó. Y un ping explícito de fallo da la alarma en el acto en lugar de esperar a que venza el plazo.
Añadir monitorización a un cron job
La versión más sencilla solo hace ping tras una ejecución correcta. El && es importante: si la tarea falla, el ping nunca se envía y el check se marca como «Perdida» al vencer el plazo.
0 3 * * * /path/to/backup.sh && curl -fsS --retry 3 https://api.alertsleep.com/v1/ping/YOUR-TOKEN
La versión completa informa del inicio, del éxito y del fallo:
0 3 * * * curl -fsS --retry 3 https://api.alertsleep.com/v1/ping/YOUR-TOKEN/start; if /path/to/backup.sh; then curl -fsS --retry 3 https://api.alertsleep.com/v1/ping/YOUR-TOKEN; else curl -fsS --retry 3 https://api.alertsleep.com/v1/ping/YOUR-TOKEN/fail; fi
Hay dos detalles intencionados. El ping de inicio va seguido de ;, no de &&, para que la tarea se ejecute igualmente aunque el ping no se pueda entregar. Y la línea usa if/then/else en lugar del popular atajo job && success || fail, que informa de un fallo cuando la tarea fue bien pero lo único que no llegó fue el ping de éxito.
También puedes enviar el exit code y dejar que decida el servicio de monitorización. Un código distinto de cero enviado a la URL de éxito se registra como fallo:
0 3 * * * /path/to/backup.sh; curl -fsS --retry 3 --data "exit_code=$?" https://api.alertsleep.com/v1/ping/YOUR-TOKEN
Merece la pena conservar las opciones de curl: -f hace que un error HTTP cuente como error, -sS no muestra nada salvo que algo vaya mal y --retry 3 aguanta un corte de red breve.
Laravel scheduler
Si la tarea ya es un comando programado de Laravel, el scheduler puede llamar a las URLs por sí mismo, sin comillas de shell y sin pasar exit codes a mano:
// routes/console.php
Schedule::command('backup:run')
->cron('0 3 * * *')
->pingBefore('https://api.alertsleep.com/v1/ping/YOUR-TOKEN/start')
->pingOnSuccess('https://api.alertsleep.com/v1/ping/YOUR-TOKEN')
->pingOnFailure('https://api.alertsleep.com/v1/ping/YOUR-TOKEN/fail');
Una cosa más a vigilar en Laravel: todo el scheduler depende de una única entrada de crontab que ejecuta php artisan schedule:run cada minuto. Si esa entrada se pierde, todas las tareas programadas se detienen a la vez, y eso es exactamente lo que te dirá un heartbeat en tu tarea más importante.
Workflows programados de GitHub Actions
Como los workflows programados pueden retrasarse, descartarse o desactivarse, necesitan un heartbeat tanto como un servidor. Guarda la URL de ping en un secret del repositorio, trátala como una contraseña y añade tres pasos:
on:
schedule:
- cron: '0 3 * * *'
jobs:
nightly:
runs-on: ubuntu-latest
steps:
- run: curl -fsS --retry 3 "${{ secrets.ALERTSLEEP_PING_URL }}/start" || true
- run: ./scripts/nightly.sh
- if: success()
run: curl -fsS --retry 3 "${{ secrets.ALERTSLEEP_PING_URL }}"
- if: failure()
run: curl -fsS --retry 3 "${{ secrets.ALERTSLEEP_PING_URL }}/fail"
El || true en el ping de inicio mantiene el workflow en marcha si el ping no se puede entregar. Una ejecución que GitHub retrasa más allá de tu periodo de gracia, o que nunca llega a arrancar, aparece como un heartbeat perdido: el único fallo que el propio workflow nunca puede notificar.
¿Cuánto debe durar el periodo de gracia?
Lo bastante largo para cubrir la variación normal de la tarea y lo bastante corto para que un problema real siga estando reciente. Una regla útil: toma la ejecución más larga que hayas visto y añade un margen. Un backup que normalmente tarda entre 20 y 40 minutos necesita alrededor de una hora; una tarea de limpieza que tarda segundos va bien con cinco minutos. Si el check aparece «Con retraso» una y otra vez sin que haya nada mal, el periodo de gracia es demasiado corto, y una alerta que casi siempre es una falsa alarma enseña a la gente a ignorarla.
Cómo monitoriza AlertSleep los cron jobs
Un check de cron en AlertSleep es una programación y una URL de ping:
- la programación es un intervalo —desde un minuto hasta 30 días— o una expresión cron con su propia zona horaria;
- el periodo de gracia puede ir de 30 segundos a 24 horas, con cinco minutos por defecto;
- la URL de ping acepta GET, POST y HEAD, con las variantes
/starty/fail, y opcionalmente un exit code, una duración y un mensaje corto; - una tarea que envió
/starty nada más dentro del periodo de gracia se marca como colgada, no como no ejecutada; - las alertas llegan por email y al panel cuando un check se marca como «Perdida», y el siguiente ping correcto va seguido de un aviso de recuperación;
- cada ping se guarda con su exit code y su duración, así puedes ver cuándo una tarea empezó a tardar más;
- el panel muestra fragmentos de crontab y Laravel listos para pegar en cada check, y el token se puede regenerar si se filtra una URL de ping.
Tienes los detalles en la página de monitorización de cron. Si todavía estás escribiendo la propia programación, el Generador de expresiones cron gratuito construye una expresión a partir de un formulario y muestra sus cinco próximas ejecuciones.
Checklist de cron en 20 minutos
- Localiza todas las tareas programadas.
crontab -lpara cada usuario, los ficheros de/etc/cron.d,systemctl list-timers, los workflows programados de CI y cualquier planificador dentro de tu aplicación. - Para cada una, pregúntate qué se rompe si no se ejecuta durante una semana. Backups, facturas, sincronizaciones de datos, renovaciones de certificados y limpiezas suelen encabezar la lista.
- Añade un heartbeat a las importantes: empieza por los backups, porque un backup fallido solo se descubre cuando necesitas restaurar.
- Define los periodos de gracia a partir de tiempos de ejecución reales, no de suposiciones.
- Pruébalo: detén una tarea a propósito, o apúntala a una ruta incorrecta, y comprueba que llega la alerta.
- Escapa cada
%en tus líneas de crontab y define explícitamente el entorno que necesita la tarea, al principio del script.
Preguntas frecuentes
¿Qué diferencia hay entre el monitoreo de cron y la monitorización de disponibilidad?
La monitorización de disponibilidad comprueba algo que siempre está ahí: un sitio web, una API, un puerto. El monitoreo de cron espera una señal de algo que solo se ejecuta de vez en cuando y avisa cuando esa señal no llega.
¿La tarea tiene que ser accesible desde internet?
No. La tarea solo hace una petición HTTPS saliente a la URL de ping. Nada tiene que conectarse a tu servidor.
¿Qué pasa si el ping no se puede entregar?
--retry 3 resuelve los problemas de red breves. Si aun así el ping no llega, el check se marca como «Perdida» al vencer el plazo y recibes una alerta. Alguna vez puede ser una falsa alarma, pero es el error en la dirección segura: un fallo real que se te escapa cuesta mucho más que un email innecesario.
¿Puedo monitorizar tareas que se ejecutan más de una vez por minuto?
El intervalo más corto es un minuto. Para tareas que se ejecutan con más frecuencia, haz ping una vez por minuto desde la tarea o monitoriza el proceso que las lanza.
¿La URL de ping es un secreto?
Sí. Cualquiera que la tenga puede marcar tu tarea como correcta. No la pongas en repositorios públicos, guárdala en un secret en CI y regenera el token si alguna vez se filtra.
Fuentes
Compartir este artículo
Sobre el autor
AlertSleep Team
Monitoring & DevOps
The AlertSleep team helps developers and ops teams keep their services online and communicate outages professionally.