Cron vs timers de systemd: cron ejecuta líneas, los timers ejecutan unidades
El mismo job de 03:30 en ambos relojes — correo silencioso frente a registro en el journal, ejecuciones saltadas frente a Persistent=true, y la tabla de decisión para saber dónde aterriza tu próximo job.
Caja systemd propia → timer. Jobs de una línea, contenedores, cajas que no montaste tú → cron.
Cron falla en silencio — manda un correo a un buzón local que nadie lee. Los timers loguean cada ejecución al journal: journalctl -u mytimer.service.
Persistent=true re-ejecuta los trabajos perdidos mientras la máquina estaba apagada ; cron los salta en silencio.
LOS DOS RELOJES
El mismo job — respaldo nocturno a las 03:30 — en ambos relojes, y lo que cada uno deja atrás:
CRON · una línea
30 3 * * * /usr/local/bin/backup.sh
se ejecuta cuando el reloj de pared coincide. Si falla, el output se va en correo a un buzón local — normalmente sin leer. ¿La máquina estaba apagada a las 03:30? Saltado, en silencio.
¿Llegó a dispararse? journalctl -u cron -n 20 o grep CRON /var/log/syslog
TIMER DE SYSTEMD · dos ficheros de unidad
# foo.timer
[Timer]
OnCalendar=*-*-* 03:30:00
Persistent=true
# foo.service — the job itself
una unidad de primera clase: estado, dependencias, logs por ejecución en el journal. ¿La máquina dormía a la hora? Persistent=true lo corre una vez al arrancar.
compruébalo: systemd-analyze calendar "*-*-* 03:30:00" — el próximo disparo, antes de guardar
el veredicto del horario: los cinco campos de cron son el incumbent compacto; OnCalendar es más largo pero estrictamente más expresivo — Mon..Fri 09..17:00:00 es una línea, y RandomizedDelaySec evita mil máquinas machacando un solo servidor
estos relojes re-estampan desde los propios comandos del expediente — no pueden leer el journal de tu máquina; dónde está realmente tu journal: secciones 04 y 05.
LAS DISTANCIAS PRÁCTICAS
- Logging : los timers loguean stdout/stderr al journal por unidad; cron, en el mejor caso, manda correo al usuario local.
- Ejecuciones perdidas : un timer con
Persistent=truese corre una vez al boot si le saltó su horario; cron simplemente salta. - Dependencias : los timers saben esperar
network-online.targeto puntos de montaje; cron te obliga a escribir a mano la lógica de reintento dentro del script. - Sintaxis : cron es una línea; un timer son dos ficheros. Ese es el coste entero del cambio.
- Opción monotónica:
OnBootSec=15min— cambios del reloj de pared no pueden confundirlo.
04PROBAR UN TIMER SIN ESPERAR
Tres comandos responden todo — lista qué está planificado y cuándo disparará la próxima vez, corre el job a mano tal como lo haría el timer, y después lee sus logs:
systemctl list-timers --all # every timer, next + last run
sudo systemctl start backup.service # fire the job now, same unit as the timer
journalctl -u backup.service -f # watch its output live
Fíjate en el reparto: systemctl start backup.timer arma el horario; el service es el job. Si list-timers muestra tu timer, systemctl status backup.service está en verde, y el journal muestra tu output, toda la cadena funciona.
verify: systemctl list-timers --all → tu timer, última y próxima ejecución listadas
05CORRER UN JOB CRON A MANO
Los jobs cron corren con un entorno despojado, por eso «funciona en mi shell, falla en cron» es un género de bug. Para reproducir cron fielmente, corre el comando a través de sh con el entorno que usaría cron:
crontab -l # confirm the exact line
env -i SHELL=/bin/sh PATH=/usr/bin:/bin sh -c '/usr/local/bin/backup.sh'
grep CRON /var/log/syslog | tail # did cron even fire it? (Debian/Ubuntu)
journalctl -u cron -n 20 # same, on systemd distros
Esa env -i es la prueba manual honesta: un entorno desnudo con el PATH por defecto de cron. Los dos clásicos de cron — un PATH desnudo y un % sin comillas (cron lo trata como un salto de línea) — aparecen de inmediato en esa prueba.
verify: journalctl -u cron -n 20 → la entrada CRON de tu comando
«MI TIMER NO SE DISPARA»
Un timer que parece muerto suele tener su service fallando al instante. Cuatro causas, en el orden para comprobarlas:
- Los nombres del service y del timer no coinciden.
foo.timerdisparafoo.service— una errata o una unidad renombrada y el timer «corre» al vacío.systemctl cat foo.timermuestra exactamente a quién apunta. - Editado pero sin recargar. Después de cambiar un fichero de unidad:
sudo systemctl daemon-reload && sudo systemctl restart foo.timer. Sin esto tu nuevo horario no está vivo. - Comando de estado equivocado.
systemctl status foo.timersiempre muestra active incluso estando ocioso — mira mejorlist-timersen su lugar. - El service que dispara está fallando al instante.
journalctl -u foo.service --since -1hmuestra el crash quelist-timersesconde.
verify: journalctl -u foo.service --since -1h → el crash que list-timers esconde
LA TABLA DE DECISIÓN
| cron | timer systemd | |
|---|---|---|
| Instalación | una línea de crontab | dos ficheros de unidad |
| Logging | correo local, normalmente sin leer | journal, por unidad |
| Ejecución perdida (máquina apagada) | saltada | se corre una vez con Persistent=true |
| Dependencias / orden | ninguna — DIY en script | dependencias de unidad completas |
| Retardo aleatorizado | DIY con $RANDOM | RandomizedDelaySec= |
| Prueba / dry-run del horario | no | systemd-analyze calendar |
| Existe en todas partes | sí — contenedores, BSD, embebidos | necesita systemd (PID 1) |
| Jobs de usuario sin root | crontab -e | systemd --user unidades |
Reglas del pulgar : llevar un job a tu propia máquina systemd → timer. Recordatorio personal rápido o una caja que no montaste → cron. Cualquier cosa con dependencias, reintentos, o la necesidad de saber si de verdad corrió → timer, siempre. El patrón clásico — un rsync nocturno de respaldo bajo un timer con Persistent=true — garantiza que el respaldo ocurre incluso si la máquina dormía en el minuto pautado, cosa que cron simplemente no puede ofrecer.
fin de los dos relojes · nada se descargó · todo es local