Ir al contenido
  1. Posts/

Cron jobs en tu homelab: automatiza lo aburrido y olvídate de hacerlo tú

Hay tareas en el homelab que hago todos los días sin pensar. O las hacía, antes de delegarlas a cron.

Backups nocturnos. Limpiar logs que se acumulan. Revisar si un servicio cayó. Regenerar un reporte de consumo. Actualizar un archivo de configuración. Cosas que si las hago manualmente están bien, pero que si me olvido durante dos semanas empiezan a causar problemas.

Cron lleva décadas siendo la solución estándar en Linux para esto. No es glamuroso. No tiene interfaz web bonita ni métricas en tiempo real. Pero funciona. Cuando llevo meses con un cron job sin tocarlo y sé que está haciendo su trabajo cada día, eso vale mucho.

Te cuento cómo lo uso, qué errores cometí al principio y qué patrones me han funcionado mejor.

La sintaxis básica (la parte que todo el mundo googlea)
#

El formato de crontab tiene fama de críptico pero en realidad es sencillo cuando lo entiendes una vez:

1
2
3
4
5
6
7
* * * * * comando-a-ejecutar
| | | | |
| | | | +--- Día de la semana (0-7, donde 0 y 7 = domingo)
| | | +----- Mes (1-12)
| | +------- Día del mes (1-31)
| +--------- Hora (0-23)
+----------- Minuto (0-59)

El asterisco significa “cualquier valor”. Así que * * * * * ejecuta el comando cada minuto de cada hora de cada día.

Algunos ejemplos concretos:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# Cada día a las 3 de la mañana
0 3 * * * /home/usuario/scripts/backup.sh

# Cada hora, en el minuto 0
0 * * * * /home/usuario/scripts/check-services.sh

# Los lunes a las 9:00
0 9 * * 1 /home/usuario/scripts/weekly-report.sh

# Cada 15 minutos
*/15 * * * * /home/usuario/scripts/ping-check.sh

# El primer día de cada mes a las 00:00
0 0 1 * * /home/usuario/scripts/monthly-cleanup.sh

Para editar el crontab del usuario actual: crontab -e. Para ver el crontab activo: crontab -l.

Consejo que me hubiera ahorrado tiempo: usa crontab.guru en el navegador para verificar que tu expresión hace lo que crees. He escrito muchos cron jobs convencido de que ejecutaban cada hora y en realidad eran cada minuto.

Mis cron jobs reales
#

Llevo tiempo refinando estos. No todos siguen activos, pero son los que más valor me han dado.

Backup nocturno de configuraciones
#

1
0 2 * * * /home/admin/scripts/backup-configs.sh >> /var/log/backup.log 2>&1

El script copia las carpetas de configuración de mis servicios Docker a un directorio en el NAS. Nada sofisticado: un rsync con algunas exclusiones. Lo que importa es que lo hace todas las noches a las 2h y yo no tengo que acordarme.

El >> /var/log/backup.log 2>&1 al final es importante. Redirige tanto stdout como stderr al log. Sin eso, los errores desaparecen en el éter y nunca sabes si algo falló.

Limpieza de imágenes Docker antiguas
#

1
0 4 * * 0 docker system prune -f >> /var/log/docker-cleanup.log 2>&1

Cada domingo a las 4h, borro imágenes sin usar, contenedores parados y redes huérfanas. Antes de tener esto, mis servidores llenaban el disco con imágenes viejas de Watchtower. Ahora el espacio se mantiene bajo control solo.

El -f es para que no pida confirmación interactiva. Sin eso, el cron job se queda esperando una tecla que nunca llega.

Verificación de servicios críticos
#

1
*/10 * * * * /home/admin/scripts/check-services.sh

Cada 10 minutos, el script hace un curl a los endpoints de mis servicios más importantes. Si alguno falla tres veces seguidas, me manda una notificación por Telegram. No es Uptime Kuma (que también uso), sino un backup simple para los servicios que Uptime Kuma a veces no alcanza.

El script es básico:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
#!/bin/bash
SERVICES=(
    "http://192.168.1.100:8080/health"
    "http://192.168.1.101:9090"
)

for url in "${SERVICES[@]}"; do
    if ! curl -s --max-time 5 "$url" > /dev/null 2>&1; then
        echo "$(date): FALLO en $url" >> /var/log/services-check.log
    fi
done

Rotación de logs
#

1
0 0 * * * find /var/log/homelab -name "*.log" -mtime +7 -delete

Logs de más de 7 días, borrados. Sin esto, algunos servicios verbose llenaban el disco en semanas.

Reporte semanal de disco
#

1
0 9 * * 1 /home/admin/scripts/disk-report.sh

Los lunes por la mañana me envía un resumen del uso de disco de todos los servidores. Lo genero con df -h y lo mando por Telegram. Cinco segundos de lectura y sé si algo está creciendo de forma rara.

El problema del entorno
#

El error más frecuente que veo en cron es que los scripts funcionan perfectamente en la terminal pero fallan silenciosamente cuando los ejecuta cron. La causa casi siempre es el mismo: el entorno.

Cuando ejecutas un comando manualmente, tienes todo tu entorno: $PATH, variables de entorno, archivos .bashrc cargados. Cron no tiene nada de eso. Su $PATH es mínimo: /usr/bin:/bin. Si tu script llama a docker, python3 o cualquier cosa que no esté en esa ruta, falla.

La solución más robusta es usar rutas absolutas en todos los scripts:

1
2
3
4
5
# Mal - puede fallar en cron
docker system prune -f

# Bien - siempre funciona
/usr/bin/docker system prune -f

O definir $PATH al principio del crontab:

1
2
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
0 3 * * * /home/admin/scripts/backup.sh

Otro problema: los scripts que usan ~ como directorio home. En cron, ~ puede no expandirse como esperas. Usa rutas absolutas: /home/usuario/scripts/ en lugar de ~/scripts/.

Capturando los errores
#

Un cron job que falla en silencio es peor que no tener cron job. Al menos si la tarea no existe sabes que tienes que hacerla tú. Si el cron job existe pero falla sin avisar, tienes una falsa sensación de seguridad.

Por defecto, cron envía los errores por email al usuario del sistema. En servidores sin servidor de correo configurado, esos emails desaparecen. Soluciones:

Redirigir a log:

1
0 3 * * * /home/admin/scripts/backup.sh >> /var/log/backup.log 2>&1

Usar el wrapper chronic (de moreutils):

1
0 3 * * * chronic /home/admin/scripts/backup.sh

chronic suprime la salida si el comando tiene éxito y solo muestra output cuando falla. Muy limpio.

Alertas activas con un script wrapper:

Para los jobs críticos, uso un wrapper que envía notificación de éxito O de fallo:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
#!/bin/bash
# run-with-alert.sh
SCRIPT=$1
JOB_NAME=$2

if $SCRIPT; then
    echo "$(date): OK - $JOB_NAME" >> /var/log/cron-results.log
else
    echo "$(date): FALLO - $JOB_NAME" >> /var/log/cron-results.log
    # Aquí iría la notificación por Telegram, webhook, etc.
fi

Y en crontab:

1
0 3 * * * /home/admin/scripts/run-with-alert.sh /home/admin/scripts/backup.sh "Backup diario"

Systemd timers como alternativa
#

En sistemas modernos con systemd (la mayoría de Debian/Ubuntu actuales), los systemd timers son una alternativa más robusta a cron. No los uso para todo, pero para servicios críticos los prefiero.

Las ventajas concretas:

  • Logs integrados en journald: journalctl -u mi-timer.service te da todo el historial
  • Control de dependencias: puedes hacer que un timer solo ejecute si otro servicio está activo
  • Manejo de tiempo perdido: si el servidor estuvo apagado, puede ejecutar el job atrasado al arrancar
  • Estado visible: systemctl list-timers te muestra todos los timers con la próxima ejecución

Para un backup simple, el timer se define con dos archivos:

/etc/systemd/system/backup.service:

1
2
3
4
5
6
7
[Unit]
Description=Backup diario de configuraciones

[Service]
Type=oneshot
ExecStart=/home/admin/scripts/backup.sh
User=admin

/etc/systemd/system/backup.timer:

1
2
3
4
5
6
7
8
9
[Unit]
Description=Timer backup diario

[Timer]
OnCalendar=*-*-* 03:00:00
Persistent=true

[Install]
WantedBy=timers.target

Activar:

1
2
systemctl enable --now backup.timer
systemctl status backup.timer

Persistent=true hace que si el servidor estaba apagado a las 3h, ejecute el backup al arrancar. Con cron esa ejecución se pierde.

Cuándo cron, cuándo n8n, cuándo otra cosa
#

Tengo cron, tengo n8n y tengo scripts ad-hoc. A veces me pregunto dónde debería ir cada cosa.

Mi criterio personal:

Uso cron cuando:

  • La tarea es simple: ejecutar un script, mover archivos, limpiar algo
  • No necesito que sea visible ni que genere reportes bonitos
  • La tarea es en un solo servidor
  • No tiene dependencias complejas

Uso n8n cuando:

  • La tarea involucra APIs externas (Slack, email, webhooks)
  • Necesito lógica condicional más compleja
  • Quiero que cualquiera pueda ver y modificar el flujo sin tocar scripts
  • La tarea coordina varios servicios

Uso cron + script Python cuando:

  • Necesito procesamiento de datos más complejo que bash
  • Quiero logs estructurados en JSON para ingestar en Grafana
  • La lógica es suficientemente compleja para merecer tests

Cron no compite con estas herramientas. Es la capa más baja: el mecanismo de disparo. Lo que haga con ese disparo ya depende del contexto.

Algunos gotchas que me han picado
#

El % en los comandos: El signo % en crontab es un carácter especial que se interpreta como nueva línea. Si tu comando o script usa % (en fechas con date +%Y-%m-%d, por ejemplo), tienes que escaparlo: \%.

1
2
3
4
5
# Esto falla
0 3 * * * echo $(date +%Y-%m-%d) > /tmp/fecha.txt

# Esto funciona
0 3 * * * echo $(date +\%Y-\%m-\%d) > /tmp/fecha.txt

Los cron jobs que se solapan: Si un job tarda más de su intervalo, cron lanza otra instancia. Para jobs que no pueden ejecutarse en paralelo, uso flock:

1
0 * * * * flock -n /tmp/mi-job.lock /home/admin/scripts/mi-job.sh

Si ya hay una instancia corriendo, flock -n falla inmediatamente en lugar de esperar.

Los cron jobs del root vs del usuario: crontab -e edita el crontab del usuario actual. Los jobs que necesitan privilegios van en el crontab de root (sudo crontab -e) o en /etc/cron.d/. Yo intento minimizar los jobs como root y prefiero dar permisos específicos via sudo cuando es posible.

La parte aburrida que más importa: los logs
#

Todo esto no vale nada si no tienes visibilidad de lo que pasa. Mis cron jobs más importantes tienen un log dedicado y reviso esos logs periódicamente, no cuando algo se rompe.

Mi estructura de logs:

1
2
3
4
5
/var/log/homelab/
├── backup.log
├── cleanup.log
├── services-check.log
└── disk-report.log

Y un script que agrega los logs de las últimas 24h en un resumen que leo cada mañana. No es sofisticado, pero me da una vista rápida de si algo fue mal durante la noche.

El tiempo que invertí en montar todo esto fue un par de tardes. El tiempo que me ahorra cada semana es bastante más.

Cron no es sexy. No tiene dashboard. Pero lleva décadas haciendo el trabajo callado en miles de millones de servidores. Hay una razón para eso.