■ la respuesta primero

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

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:

  1. Los nombres del service y del timer no coinciden. foo.timer dispara foo.service — una errata o una unidad renombrada y el timer «corre» al vacío. systemctl cat foo.timer muestra exactamente a quién apunta.
  2. 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.
  3. Comando de estado equivocado. systemctl status foo.timer siempre muestra active incluso estando ocioso — mira mejor list-timers en su lugar.
  4. El service que dispara está fallando al instante. journalctl -u foo.service --since -1h muestra el crash que list-timers esconde.

verify: journalctl -u foo.service --since -1h → el crash que list-timers esconde

LA TABLA DE DECISIÓN

crontimer systemd
Instalaciónuna línea de crontabdos ficheros de unidad
Loggingcorreo local, normalmente sin leerjournal, por unidad
Ejecución perdida (máquina apagada)saltadase corre una vez con Persistent=true
Dependencias / ordenninguna — DIY en scriptdependencias de unidad completas
Retardo aleatorizadoDIY con $RANDOMRandomizedDelaySec=
Prueba / dry-run del horarionosystemd-analyze calendar
Existe en todas partessí — contenedores, BSD, embebidosnecesita systemd (PID 1)
Jobs de usuario sin rootcrontab -esystemd --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.

■ sello honesto Probado en: systemd (PID 1) · timers · cron · journalctl · systemd-analyze. Primera publicación 2026-09-11, ficha actualizada el 2026-10-04. Cada comando corre tal como está impreso; los comportamientos de fallo silencioso (el correo local de cron, el catch-up de Persistent=true al boot, list-timers versus status) son los defaults documentados de cron y systemd, tecleados desde timers que de verdad corren.

fin de los dos relojes · nada se descargó · todo es local