De cron a systemd timers: cuándo migrar y cómo hacerlo bien
Cron sigue siendo válido, pero los timers de systemd resuelven sus limitaciones más molestas: logs, dependencias, ejecución perdida y policies de reinicio.
Cron: bueno para lo simple
Cron nació en 1975 y sigue siendo válido para tareas simples: backup diario, rotación de logs, ping de healthcheck. La sintaxis minuto hora día mes diasem comando la conoce todo administrador y funciona en cualquier Unix. Nuestro generador de expresiones cron ayuda a construir la línea correcta.
Sin embargo, cron acumula limitaciones que en 2026 empiezan a pesar.
Los límites reales de cron
1. Logging escaso — si no rediriges stdout/stderr, no sabes qué pasó cuando algo falla. 2. Sin dependencias — no puedes decir "ejecuta este job después de aquel". 3. Ejecuciones perdidas — si el servidor estaba apagado a la hora del disparo, la tarea no se ejecuta cuando vuelve. 4. Sin política de reinicio — si falla, no reintenta. 5. Sin límites de recursos — el job puede comerse toda la RAM. 6. Sin aislamiento — corre con acceso completo al filesystem.
Un timer systemd mínimo
Un timer necesita dos units: uno .service con la tarea, otro .timer con la programación.
# /etc/systemd/system/backup.service
[Unit]
Description=Backup diario
[Service]
Type=oneshot
User=backup
ExecStart=/opt/scripts/backup.sh
Nice=15
IOSchedulingClass=best-effort
IOSchedulingPriority=7
# /etc/systemd/system/backup.timer
[Unit]
Description=Ejecutar backup diario
[Timer]
OnCalendar=*-*-* 03:00:00
RandomizedDelaySec=15min
Persistent=true
[Install]
WantedBy=timers.target
Activa:
sudo systemctl daemon-reload
sudo systemctl enable --now backup.timer
systemctl list-timers # ver próximos disparos
journalctl -u backup.service -f
Sintaxis OnCalendar: mucho más legible
daily— cada día a las 00:00hourly— al minuto 0 de cada hora--* 03:00:00— cada día a las 3Mon..Fri --* 09:00— lunes a viernes a las 9*:0/15— cada 15 minutosMon --* 06:00— cada lunes a las 6
Analiza cualquier expresión con systemd-analyze calendar '<expresión>'.
Persistent: no perder ejecuciones
Persistent=true guarda el último disparo en disco. Si el servidor estaba apagado a la hora de la ejecución, en cuanto arranca ejecuta la tarea pendiente. Cron nunca supo hacer esto sin ayudas como anacron.
RandomizedDelaySec: jitter contra colisiones
Si 100 servidores ejecutan la misma tarea a las 3:00, saturan la red y el destino de backups. RandomizedDelaySec=15min reparte los disparos en una ventana de 15 minutos.
Aislamiento y hardening por defecto
Un service de systemd puede aplicar todas las directivas de sandboxing: ProtectSystem=strict, NoNewPrivileges=yes, MemoryMax=1G, PrivateNetwork=yes si no necesita red. Un cron job hereda tu entorno con acceso total.
Cuándo NO migrar
- Scripts triviales de usuario en
~/— sigue concrontab -e. - Sistemas sin systemd (Alpine, algunos BSDs, muchos containers minimalistas).
- Cuando otra persona hará mantenimiento y solo conoce cron.
Guía rápida de migración
1. Identifica cada línea de tu crontab. 2. Genera el par service/timer correspondiente (o usa nuestro generador systemd). 3. Prueba con systemctl start miservicio.service que la ejecución manual funcione. 4. Activa el timer con systemctl enable --now. 5. Comprueba en systemctl list-timers la próxima ejecución. 6. Elimina la línea del crontab.
Alternativas más allá de cron y systemd
- Kubernetes CronJob — si tu tarea vive en un cluster, define un CronJob y olvídate de programar en cada nodo.
- Rundeck, Airflow, Temporal — para dependencias complejas entre tareas, retries inteligentes, UI y auditoría.
- Cronitor, Healthchecks.io — servicios de monitorización externa que te avisan si un job no se ejecutó.
Para el 90% de tareas de un sysadmin en un servidor tradicional, los timers de systemd son el sweet spot: la simplicidad de cron con las capacidades de un scheduler moderno.
Migraciones traducidas línea a línea
Backup diario a las 3:00
0 3 * * * /usr/local/bin/backup.sh
# /etc/systemd/system/backup.timer
[Unit]
Description=Backup diario
[Timer]
OnCalendar=*-*-* 03:00:00
Persistent=true
RandomizedDelaySec=300
[Install]
WantedBy=timers.target
Persistent=true ejecuta el backup al arrancar si el servidor estaba apagado a las 3:00 — algo que cron nunca hace. RandomizedDelaySec evita que cien servidores golpeen el almacenamiento en el mismo segundo.
Cada 5 minutos
*/5 * * * * /usr/local/bin/check.sh
[Timer]
OnCalendar=*:0/5
AccuracySec=1s
Lunes a viernes a las 8:30
30 8 * * 1-5 /usr/local/bin/informe.sh
[Timer]
OnCalendar=Mon..Fri 08:30
Primer día de mes
0 0 1 * * /usr/local/bin/factura.sh
[Timer]
OnCalendar=*-*-01 00:00:00
10 minutos después de cada arranque
@reboot en cron se traduce a:
[Timer]
OnBootSec=10min
Si prefieres partir de la expresión cron, escríbela primero en el generador de crontab y genera el par service/timer con el generador de systemd.
Verificar antes de confiar
systemd-analyze calendar "Mon..Fri 08:30" # muestra la próxima ejecución
systemctl list-timers --all # todos los timers y su next/last
journalctl -u backup.service -n 50 # salida real del último run
systemctl status backup.timer
systemd-analyze calendar es el equivalente honesto de "probar" un cron: te dice exactamente cuándo se disparará, sin esperar un día para descubrir que te equivocaste de campo.
Errores frecuentes al migrar
- Olvidar
WantedBy=timers.targeten[Install]:enableno hace nada y el timer nunca arranca. - Habilitar el service en lugar del timer: se ejecuta en cada arranque y no según calendario.
- Suponer el
PATHde tu shell: el service no carga tu.bashrc. Usa rutas absolutas oEnvironment=. - No poner
Type=oneshoten el service de una tarea puntual: systemd lo tratará como daemon que ha muerto. - Dejar la línea en el crontab tras migrar: la tarea se ejecuta dos veces.
- No revisar la zona horaria:
OnCalendarusa la del sistema; añadePersistent=truey comprueba contimedatectl.
Preguntas frecuentes
?¿Qué ventajas tienen los timers de systemd sobre cron?
Logs integrados en journalctl, dependencias entre unidades, ejecución recuperada tras un apagado con Persistent=true, límites de recursos, sandboxing y la posibilidad de lanzar la tarea a mano con systemctl start.
?¿Cómo traduzco una expresión cron a OnCalendar?
Cada campo tiene equivalente: */5 * * * * pasa a OnCalendar=*:0/5 y 30 8 * * 1-5 pasa a OnCalendar=Mon..Fri 08:30. Verifica el resultado con systemd-analyze calendar antes de activarlo.
?¿Cómo pruebo un timer sin esperar a su hora?
Ejecuta systemctl start nombre.service para probar la tarea y systemd-analyze calendar para confirmar la próxima ejecución del timer.
?¿Debo habilitar el .timer o el .service?
El .timer con systemctl enable --now nombre.timer. El service se lanza solo cuando el timer lo dispara.
?¿Qué hace Persistent=true?
Ejecuta la tarea en el siguiente arranque si el sistema estaba apagado en el momento previsto. Cron simplemente se salta esa ejecución.
?¿Merece la pena migrar todos los cron jobs?
No siempre. Para scripts triviales de usuario o sistemas sin systemd (Alpine, contenedores minimalistas) cron sigue siendo la opción más simple.