Systemd timers: lo que cron nunca pudo hacer
El problema con cron
Usé cron por años. Funciona. Pero tiene tres problemas que me cansaron:
- Si la máquina está apagada a la hora del cronjob, se pierde. No hay reintento.
- Los logs dependen de que tú redirijas la salida. Si no lo hiciste, no sabes si falló.
- No hay forma nativa de decir “ejecuta esto solo si el servicio X está corriendo”.
systemd timers resuelve los tres. Y varios más.
Lo básico
Un timer son dos archivos: el timer (cuándo) y el servicio (qué). El servicio es un unit común de systemd. El timer le dice cuándo dispararlo.
# /etc/systemd/system/limpieza.timer
[Unit]
Description=Limpieza semanal de temporales
[Timer]
OnCalendar=weekly
Persistent=true
[Install]
WantedBy=timers.target
Persistent=true es lo que cron no tiene: si el server estuvo apagado el domingo a las 3 AM, el timer se dispara apenas bootea. No se pierde la ejecución.
Calendarios que no duelen
La sintaxis de OnCalendar= es más legible que los 5 campos de cron:
# Todos los días 03:00
OnCalendar=daily
# Lunes y viernes 08:30
OnCalendar=Mon,Fri *-*-* 08:30:00
# Cada 15 minutos
OnCalendar=*:0/15
# Primer domingo del mes, 02:00
OnCalendar=Sun *-*-1..7 02:00:00
Para validar una expresión sin desplegar nada:
systemd-analyze calendar "Mon,Fri *-*-* 08:30:00" --iterations=5
Te escupe las próximas 5 fechas. Útil para no despertar a las 3 AM un sábado por error de tipeo.
Monotónicos: cuando no importa la hora
A veces no necesitas un calendario. Necesitas “10 minutos después de bootear” o “cada 6 horas desde la última ejecución”. Para eso están los timers monotónicos:
| Directiva | Se dispara… |
|---|---|
OnBootSec= | X tiempo después del boot |
OnActiveSec= | X tiempo después de activar el timer |
OnUnitActiveSec= | X tiempo después de la última ejecución |
OnUnitInactiveSec= | X tiempo después de que terminó la última ejecución |
Caso real: limpiar /tmp 10 minutos después del boot, y luego cada 6 horas:
[Timer]
OnBootSec=10min
OnUnitActiveSec=6h
Sin cron, sin scripts wrapper, sin sleep. Dos líneas.
Aleatorización para no tumbarte el NAS
Si administras 50 servidores y todos hacen backup a las 3 AM contra el mismo NAS, lo saturas. Con cron la solución era sleep $((RANDOM % 1800)) && backup.sh. Con systemd:
[Timer]
OnCalendar=*-*-* 03:00:00
RandomizedDelaySec=1800
El timer calcula un retardo aleatorio entre 0 y 1800 segundos. Lo mantiene constante hasta que reinicie systemd. Tus 50 backups se distribuyen solos en una ventana de 30 minutos.
Dependencias: que no se dispare si no hay internet
El timer puede exigir que ciertos targets o servicios estén activos:
[Unit]
Description=Backup diario a la nube
After=network-online.target
Wants=network-online.target
[Timer]
OnCalendar=daily
Y el servicio asociado puede ser más granular. Si tu backup necesita PostgreSQL corriendo:
# En backup.service (no en el .timer)
[Unit]
After=postgresql.service
Requires=postgresql.service
Si PostgreSQL no está activo, el timer se dispara igual, pero el servicio falla. Y systemd lo registra. No hay silencio cómplice como en cron.
Debugging sin adivinar
¿Cuándo se dispara la próxima vez?
systemctl list-timers --all | grep backup
Devuelve una tabla con NEXT, LEFT, LAST, PASSED. Sin parsear logs ni revisar crontabs.
¿Falló la última ejecución?
systemctl show backup.service -p ExecMainStatus
Si no es 0, algo pasó. Para ver qué:
journalctl -u backup.service --since "1 day ago"
Forzar ejecución manual
systemctl start backup.service
Esto ejecuta el servicio ya. No toca el timer. No reinicia el ciclo. Ideal para tests.
Cron vs systemd timer
| Funcionalidad | cron | systemd timer |
|---|---|---|
| Calendarios | Sí | Sí |
| Eventos relativos (boot, última ejecución) | No | Sí |
| Persistencia tras boot | Se pierde | Persistent=true |
| Aleatorización | Manual | Nativa |
| Dependencias de servicios | No | After=, Requires= |
| Logs | Manual (redirigir salida) | journalctl automático |
| Seguridad (aislamiento) | No | PrivateTmp=, ProtectSystem=, etc |
| Debugging | Buscar en logs | systemctl list-timers, journalctl |
Una verificación rápida: ¿tu sistema ya migró los cronjobs a timers?
ls /usr/lib/systemd/system/*.timer
En Ubuntu 22.04 aparecen logrotate.timer, man-db.timer, apt-daily.timer, fstrim.timer. Los propios maintainers de la distro ya hicieron el cambio.
Si tu sistema corre systemd (Ubuntu 16.04+, Debian 8+, RHEL 7+), no hay razón técnica para seguir escribiendo cronjobs nuevos. Los que ya tienes funcionando déjalos. Pero lo nuevo, en timer.