Cron vs timers de systemd: cuál deberías usar
Cron vs timers de systemd: sintaxis, logging, ejecuciones perdidas y dependencias — más una tabla de decisión para que tu próxima tarea caiga donde debe.
En una distro moderna, usa un timer de systemd para todo lo que es tuyo y reserva cron para tareas de una línea y servidores que no montaste tú. Los timers registran cada ejecución en el journal, pueden recuperar schedules perdidos mientras la máquina estaba apagada y dependen de los mismos unit files que todo lo demás del sistema. Cron gana en brevedad — una línea de crontab -e vence a dos unit files — y sigue siendo el único scheduler garantizado en contenedores mínimos y cajas Unix exóticas. La trampa: cron falla en silencio. Si tu tarea falla a las 3 a.m., cron le escribe a un mailbox local que nadie lee, mientras que un timer te da journalctl -u mytimer.service con la salida completa. Abajo: las diferencias reales, cómo probar cada uno, por qué un timer puede no dispararse y una tabla de decisión.
¿Cuál es la diferencia entre cron y un timer de systemd?
Cron es un daemon que lee una tabla de líneas — cinco campos de tiempo y un comando — y ejecuta cada comando cuando el reloj coincide. Ese es todo el modelo. No tiene concepto de una tarea como objeto: ni unit, ni estado, ni dependencias, ni entrada de log propia.
Un timer de systemd es un unit file (foo.timer) que dispara otro unit file (foo.service) cuando su schedule coincide. La tarea es un objeto de primera clase con start, status, logs y seguimiento de fallos como cualquier otro servicio. La programación es por calendario (tipo cron) o monotónica (OnBootSec=15min, que los cambios de reloj de pared no pueden confundir).
Las consecuencias prácticas:
- Logging: los timers mandan stdout/stderr al journal por unit; cron como mucho le escribe un mail al usuario local.
- Ejecuciones perdidas: un timer con
Persistent=truecorre una vez al boot si se pasó su schedule; cron simplemente la salta. - Dependencias: los timers pueden esperar
network-online.targeto puntos de montaje; cron te obliga a escribir la lógica de reintento a mano en el script. - Sintaxis: cron es una línea; un timer son dos archivos. Ese es todo el costo de cambiar.
¿Qué sintaxis de schedule es más fácil — crontab o OnCalendar?
Los cinco campos de cron son el incumbente compacto: */15 * * * * es cada 15 minutos y la mayoría de los admins los lee dormidos. El OnCalendar= de systemd es más verboso pero estrictamente más expresivo, y systemd-analyze calendar te dice las próximas ejecuciones antes de que te comprometas — cron no tiene un dry-run equivalente.
# Cron: todos los días a las 03:30
30 3 * * * /usr/local/bin/backup.sh
# systemd: el mismo schedule, verificable antes de guardarlo
systemd-analyze calendar "*-*-* 03:30:00"
# -> Próxima ejecución: vie 2026-09-11 03:30:00 ...
OnCalendar maneja cosas que cron genuinamente no puede expresar bien: Mon..Fri *-*-* 09..17:00:00 (días de semana, horario laboral) o *:0/15 con RandomizedDelaySec=10m para que mil máquinas no golpeen un servidor en el mismo instante.
¿Cómo pruebo un timer de systemd sin esperar?
Tres comandos responden todo. Lista lo programado y cuándo dispara la próxima vez, corre la tarea a mano exactamente como lo haría el timer y luego lee sus logs:
systemctl list-timers --all # cada timer, próxima + última ejecución
sudo systemctl start backup.service # dispara la tarea ya, el mismo unit del timer
journalctl -u backup.service -f # mira su salida en vivo
Fíjate en la división: systemctl start backup.timer arma el schedule; el service es la tarea. Si list-timers muestra tu timer, systemctl status backup.service está en verde y el journal muestra tu salida, toda la cadena funciona.
¿Cómo ejecuto una tarea de cron manualmente?
Las tareas de cron corren con un entorno recortado, y por eso "funciona en mi shell, falla en cron" es todo un género de bug. Para reproducir cron fielmente, corre el comando vía sh con el mismo entorno que usaría cron:
crontab -l # confirma la línea exacta
env -i SHELL=/bin/sh PATH=/usr/bin:/bin sh -c '/usr/local/bin/backup.sh'
grep CRON /var/log/syslog | tail # ¿cron siquiera la disparó? (Debian/Ubuntu)
journalctl -u cron -n 20 # lo mismo, en distros con systemd
Esa línea con env -i es la prueba manual honesta: un entorno pelado con el PATH default de cron. La mayoría de los fallos silenciosos de cron son un PATH pelado o un % sin escapar en el comando (cron trata % como un salto de línea), y ambos aparecen de inmediato así.
¿Por qué mi timer de systemd no se dispara?
Cuatro causas cubren casi todos los casos, en el orden de revisión:
- El nombre del service y del timer no coinciden.
foo.timerdisparafoo.service— unOnFailuremal escrito o un unit renombrado hace que el timer "corra" hacia la nada.systemctl cat foo.timermuestra exactamente a qué apunta. - El timer se editó pero no se recargó. Tras cambiar un unit file:
sudo systemctl daemon-reload && sudo systemctl restart foo.timer. Sin esto tu schedule nuevo no está vivo. Persistent=truecon semántica deOnCalendardistinta a la que esperas — o estás mirandosystemctl status foo.timer(siempre muestra active aunque esté inactivo) en vez delist-timers.- El service que dispara falla al instante, así que el timer parece muerto.
journalctl -u foo.service --since -1hmostrará el crash quelist-timersesconde.
Y cuando vayas a mirar los logs, el cheat sheet de journalctl cubre los filtros (-u, --since, -f) que hacen esto rápido.
Cron vs timers de systemd: la tabla de decisión
| cron | timer de systemd | |
|---|---|---|
| Puesta en marcha | una línea de crontab |
dos unit files |
| Logging | mail local, normalmente sin leer | journal, por unit |
| Ejecución perdida (máquina apagada) | se salta | corre una vez con Persistent=true |
| Dependencias / orden | ninguna — DIY en el script | dependencias completas de unit |
| Retraso aleatorio | DIY con $RANDOM |
RandomizedDelaySec= |
| Probar / dry-run del schedule | no | systemd-analyze calendar |
| Existe en todas partes | sí — contenedores, BSDs, embebidos | necesita systemd (PID 1) |
| Tareas de usuario sin root | crontab -e |
units de systemd --user |
Reglas generales: lanzar una tarea en tu propia máquina con systemd → timer. Un recordatorio personal rápido o una caja que no montaste → cron. Cualquier cosa con dependencias, reintentos o necesidad de saber si de verdad corrió → timer, siempre. Un patrón común es un timer que corre una tarea de mantenimiento — digamos un backup con rsync nocturno — donde Persistent=true garantiza que el backup ocurre aunque la máquina durmiera en el minuto programado, algo que cron sencillamente no puede ofrecer.