--- title: "Cómo usar timers de systemd en lugar de cron en Ubuntu 24.04" description: "Crea timers de systemd para programar tareas en Ubuntu 24.04: servicio y timer, sintaxis OnCalendar, logs con journalctl y cómo migrar tus trabajos de cron." category: "Herramientas de Automatización" language: es source: https://cubepath.com/es/docs/herramientas-de-automatización/systemd-timers-como-alternativa-a-cron updated: 2026-09-25 --- # Cómo usar timers de systemd en lugar de cron en Ubuntu 24.04 Los timers de systemd son la alternativa moderna a cron para ejecutar tareas programadas en Linux. Cada tarea se define con dos unidades: un servicio que dice qué ejecutar y un timer que dice cuándo. A cambio de escribir un archivo más, obtienes logs en el journal, ejecución de tareas perdidas si el servidor estaba apagado, límites de recursos y dependencias entre servicios. En este tutorial crearás en Ubuntu 24.04 un timer que hace una copia diaria de `/etc`, aprenderás la sintaxis de `OnCalendar`, crearás un timer de usuario y verás cómo traducir una línea de crontab a un timer. ## Requisitos previos - Un servidor con Ubuntu 24.04 LTS, por ejemplo un VPS de CubePath. Todo funciona igual en Debian 12 y Rocky Linux 9, que también usan systemd. - Un usuario no root con privilegios `sudo`. ## Cron frente a timers de systemd Antes de migrar, conviene saber qué ganas y qué pierdes: | Característica | cron | timers de systemd | |---|---|---| | Definición | Una línea en el crontab | Un `.service` y un `.timer` | | Logs | Correo local o redirección manual | Journal (`journalctl -u`) | | Tareas perdidas con el equipo apagado | Se pierden | Se ejecutan al arrancar con `Persistent=true` | | Evitar ejecuciones solapadas | Manual (`flock`) | Automático: si el servicio sigue activo, no se lanza otra vez | | Límites de CPU y memoria | No | Sí (`CPUQuota=`, `MemoryMax=`...) | | Ejecución manual para probar | Copiar el comando | `systemctl start tarea.service` | | Intervalos relativos ("15 min tras el arranque") | No | `OnBootSec=`, `OnUnitActiveSec=` | Cron sigue siendo cómodo para tareas triviales. Los timers compensan cuando la tarea es importante y quieres saber si falló. ## Paso 1: Crear el script de la tarea La tarea de ejemplo comprime `/etc` en `/var/backups/etc` y borra las copias de más de 7 días. Crea el script: ```bash sudo nano /usr/local/bin/backup-etc.sh ``` ```bash #!/usr/bin/env bash set -euo pipefail DEST="/var/backups/etc" KEEP_DAYS=7 mkdir -p "$DEST" tar -czf "$DEST/etc-$(date +%F_%H%M).tar.gz" -C / etc find "$DEST" -name 'etc-*.tar.gz' -type f -mtime +"$KEEP_DAYS" -delete echo "Copia de /etc completada en $DEST" ``` Hazlo ejecutable: ```bash sudo chmod 755 /usr/local/bin/backup-etc.sh ``` ## Paso 2: Crear la unidad de servicio El servicio describe qué se ejecuta. Usa `Type=oneshot`, el tipo adecuado para tareas que se ejecutan y terminan: ```bash sudo nano /etc/systemd/system/backup-etc.service ``` ```ini [Unit] Description=Copia de seguridad de /etc [Service] Type=oneshot ExecStart=/usr/local/bin/backup-etc.sh Nice=10 IOSchedulingClass=idle ``` `Nice=10` e `IOSchedulingClass=idle` bajan la prioridad de CPU y disco para que la copia no afecte a los servicios en producción. El servicio no lleva sección `[Install]`: no se habilita por sí solo, lo activa el timer. Recarga systemd y ejecuta el servicio a mano para comprobar que funciona antes de programarlo: ```bash sudo systemctl daemon-reload sudo systemctl start backup-etc.service journalctl -u backup-etc.service -n 5 --no-pager ``` ```output sep 25 10:12:03 servidor systemd[1]: Starting backup-etc.service - Copia de seguridad de /etc... sep 25 10:12:04 servidor backup-etc.sh[2143]: Copia de /etc completada en /var/backups/etc sep 25 10:12:04 servidor systemd[1]: backup-etc.service: Deactivated successfully. sep 25 10:12:04 servidor systemd[1]: Finished backup-etc.service - Copia de seguridad de /etc. ``` Comprueba que el archivo existe: ```bash ls -lh /var/backups/etc/ ``` ## Paso 3: Crear el timer El timer tiene el mismo nombre que el servicio pero con extensión `.timer`, y así sabe qué unidad activar: ```bash sudo nano /etc/systemd/system/backup-etc.timer ``` ```ini [Unit] Description=Copia diaria de /etc a las 02:00 [Timer] OnCalendar=*-*-* 02:00:00 Persistent=true RandomizedDelaySec=15min [Install] WantedBy=timers.target ``` - `OnCalendar` fija la hora de ejecución (todos los días a las 02:00). - `Persistent=true` guarda la hora de la última ejecución en disco; si el servidor estaba apagado a las 02:00, la tarea se lanza en cuanto arranca. - `RandomizedDelaySec` añade un retraso aleatorio de hasta 15 minutos para que varios servidores no empiecen a la vez. Recarga systemd y habilita el timer para que se inicie ahora y en cada arranque: ```bash sudo systemctl daemon-reload sudo systemctl enable --now backup-etc.timer ``` Fíjate en que se habilita el `.timer`, no el `.service`. ## Paso 4: Verificar el timer Lista los timers activos: ```bash systemctl list-timers backup-etc.timer ``` ```output NEXT LEFT LAST PASSED UNIT ACTIVATES Sat 2026-09-26 02:07:41 UTC 15h left - - backup-etc.timer backup-etc.service 1 timers listed. ``` La columna `NEXT` ya incluye el retraso aleatorio. Para ver todos los timers del sistema, incluidos los de paquetes como `apt-daily.timer` o `logrotate.timer`, ejecuta `systemctl list-timers --all`. El estado detallado muestra cuándo toca la próxima ejecución: ```bash systemctl status backup-etc.timer ``` ```output ● backup-etc.timer - Copia diaria de /etc a las 02:00 Loaded: loaded (/etc/systemd/system/backup-etc.timer; enabled; preset: enabled) Active: active (waiting) since Fri 2026-09-25 10:15:12 UTC; 8s ago Trigger: Sat 2026-09-26 02:07:41 UTC; 15h left Triggers: ● backup-etc.service ``` Al día siguiente, los logs de la ejecución automática están en el journal del servicio: ```bash journalctl -u backup-etc.service --since yesterday ``` ## Paso 5: Entender la sintaxis de OnCalendar `OnCalendar` usa el formato `DíaSemana Año-Mes-Día Hora:Minuto:Segundo`. Cualquier campo puede ser `*`, una lista separada por comas, un rango con `..` o un intervalo con `/`. Estos son los casos más habituales: | Expresión | Significado | |---|---| | `hourly` | Cada hora en punto | | `daily` | Cada día a las 00:00 | | `weekly` | Cada lunes a las 00:00 | | `*-*-* 02:30:00` | Cada día a las 02:30 | | `*:0/15` | Cada 15 minutos (00, 15, 30, 45) | | `Mon..Fri *-*-* 09:00:00` | De lunes a viernes a las 09:00 | | `Sat,Sun *-*-* 10:00:00` | Sábados y domingos a las 10:00 | | `*-*-01 03:00:00` | El día 1 de cada mes a las 03:00 | | `*-*-* 08,20:00:00` | Cada día a las 08:00 y a las 20:00 | Antes de usar una expresión, compruébala con `systemd-analyze calendar`. Te devuelve la forma normalizada y las próximas ejecuciones: ```bash systemd-analyze calendar --iterations=3 'Mon..Fri *-*-* 09:00:00' ``` ```output Original form: Mon..Fri *-*-* 09:00:00 Normalized form: Mon..Fri *-*-* 09:00:00 Next elapse: Mon 2026-09-28 09:00:00 UTC (in UTC): Mon 2026-09-28 09:00:00 UTC From now: 2 days left Iteration #2: Tue 2026-09-29 09:00:00 UTC (in UTC): Tue 2026-09-29 09:00:00 UTC From now: 3 days left Iteration #3: Wed 2026-09-30 09:00:00 UTC (in UTC): Wed 2026-09-30 09:00:00 UTC From now: 4 days left ``` Las horas se interpretan en la zona horaria del sistema. Compruébala con `timedatectl` si la tarea debe correr a una hora local concreta. ### Timers relativos Además de horas de calendario, un timer puede contar desde un evento. Este bloque ejecuta el servicio 5 minutos después del arranque y luego cada hora desde la última ejecución: ```ini [Timer] OnBootSec=5min OnUnitActiveSec=1h ``` Es útil para tareas de sincronización o limpieza en las que no importa la hora exacta, solo la frecuencia. ## Paso 6: Añadir límites y aviso de fallos Como la tarea es un servicio normal, puedes aplicarle cualquier directiva de systemd. Para limitar recursos y cortar la tarea si se cuelga, añade al bloque `[Service]` de `/etc/systemd/system/backup-etc.service`: ```ini CPUQuota=50% MemoryMax=512M TimeoutStartSec=30min ``` Para los servicios `oneshot`, `TimeoutStartSec` es el tiempo máximo que puede durar la ejecución completa. Recarga systemd tras cualquier cambio en las unidades: ```bash sudo systemctl daemon-reload ``` Si una ejecución falla, el servicio queda en estado `failed`. Puedes listar las tareas fallidas de todo el sistema con: ```bash systemctl --failed ``` Para recibir un aviso activo, systemd permite lanzar otra unidad cuando una falla con `OnFailure=` en la sección `[Unit]`, por ejemplo un servicio que envíe un correo o un mensaje a tu sistema de alertas. ## Paso 7: Crear un timer de usuario Un usuario sin privilegios puede tener sus propios timers en `~/.config/systemd/user/`, gestionados con `systemctl --user`. Crea el directorio y un servicio que limpie archivos temporales antiguos de su directorio personal: ```bash mkdir -p ~/.config/systemd/user nano ~/.config/systemd/user/limpiar-tmp.service ``` ```ini [Unit] Description=Borrar archivos de ~/tmp con más de 14 días [Service] Type=oneshot ExecStart=/usr/bin/find %h/tmp -type f -mtime +14 -delete ``` `%h` es el especificador de systemd para el directorio personal del usuario. Crea el timer: ```bash nano ~/.config/systemd/user/limpiar-tmp.timer ``` ```ini [Unit] Description=Limpieza semanal de ~/tmp [Timer] OnCalendar=weekly Persistent=true [Install] WantedBy=timers.target ``` Habilítalo: ```bash mkdir -p ~/tmp systemctl --user daemon-reload systemctl --user enable --now limpiar-tmp.timer systemctl --user list-timers ``` Por defecto, el administrador de servicios del usuario solo vive mientras el usuario tiene una sesión abierta. Para que sus timers se ejecuten aunque no haya iniciado sesión, activa el modo *linger*: ```bash sudo loginctl enable-linger your_user ``` Sustituye `your_user` por el nombre del usuario. ## Paso 8: Migrar trabajos de cron Revisa los trabajos actuales del usuario y del sistema: ```bash crontab -l sudo crontab -l ls /etc/cron.d/ ``` La traducción de las expresiones de cron más comunes es directa: | crontab | OnCalendar | |---|---| | `0 2 * * *` | `*-*-* 02:00:00` | | `*/15 * * * *` | `*:0/15` | | `0 9 * * 1-5` | `Mon..Fri *-*-* 09:00:00` | | `0 3 1 * *` | `*-*-01 03:00:00` | | `0 0 * * 0` | `Sun *-*-* 00:00:00` | | `@reboot` | `OnBootSec=1min` (en lugar de `OnCalendar`) | Para cada trabajo, crea el par `.service` y `.timer` como en los pasos 2 y 3, prueba el servicio con `systemctl start`, habilita el timer y, solo cuando hayas comprobado la primera ejecución en `journalctl`, borra la línea del crontab con `crontab -e`. Así nunca tendrás la tarea duplicada ni sin programar. Ten en cuenta dos diferencias al migrar: - Cron ejecuta los comandos a través de `/bin/sh`, así que las redirecciones y tuberías funcionan en la línea del crontab. `ExecStart=` no usa una shell: pon esa lógica en un script, o usa `ExecStart=/bin/sh -c '...'`. - Cron carga un entorno mínimo. En un servicio puedes definir variables con `Environment=` o `EnvironmentFile=`. ### Tareas puntuales con systemd-run Para ejecutar algo una sola vez más tarde, sin crear archivos, `systemd-run` crea un timer transitorio: ```bash sudo systemd-run --on-active=30min /usr/local/bin/backup-etc.sh ``` El timer desaparece después de ejecutarse y aparece en `systemctl list-timers` mientras está pendiente. ## Solución de problemas - **El timer no aparece en `list-timers`**: comprueba que habilitaste el `.timer` con `enable --now` y que ejecutaste `daemon-reload` después de crearlo. `systemctl status nombre.timer` indica si está `inactive`. - **`Unit backup-etc.service not found`**: el timer busca un servicio con su mismo nombre. Si los nombres son distintos, indícalo con `Unit=otro.service` en la sección `[Timer]`. - **El servicio falla con `status=203/EXEC`**: la ruta de `ExecStart` no existe o el script no es ejecutable. Revisa la ruta absoluta y los permisos, y que la primera línea sea un shebang válido. - **Error en la expresión de calendario**: `journalctl -u nombre.timer` muestra `Failed to parse calendar specification`. Valida la expresión con `systemd-analyze calendar`. ## Conclusión Has creado un timer de sistema con su servicio, lo has verificado con `systemctl list-timers` y `journalctl`, has aprendido la sintaxis de `OnCalendar` y has visto cómo migrar trabajos de cron sin perder ejecuciones. A partir de aquí puedes: - Convertir tus copias de seguridad de bases de datos en timers con límites de recursos y `OnFailure=` para recibir alertas. - Consultar `man systemd.timer` y `man systemd.time` para todas las opciones de temporización. - Revisar los timers que ya trae Ubuntu (`apt-daily`, `fstrim`, `logrotate`) como ejemplos reales.