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ísticacrontimers de systemd
DefiniciónUna línea en el crontabUn .service y un .timer
LogsCorreo local o redirección manualJournal (journalctl -u)
Tareas perdidas con el equipo apagadoSe pierdenSe ejecutan al arrancar con Persistent=true
Evitar ejecuciones solapadasManual (flock)Automático: si el servicio sigue activo, no se lanza otra vez
Límites de CPU y memoriaNoSí (CPUQuota=, MemoryMax=...)
Ejecución manual para probarCopiar el comandosystemctl start tarea.service
Intervalos relativos ("15 min tras el arranque")NoOnBootSec=, 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:

sudo nano /usr/local/bin/backup-etc.sh
#!/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:

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:

sudo nano /etc/systemd/system/backup-etc.service
[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:

sudo systemctl daemon-reload
sudo systemctl start backup-etc.service
journalctl -u backup-etc.service -n 5 --no-pager
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:

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:

sudo nano /etc/systemd/system/backup-etc.timer
[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:

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:

systemctl list-timers backup-etc.timer
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:

systemctl status backup-etc.timer
● 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:

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ónSignificado
hourlyCada hora en punto
dailyCada día a las 00:00
weeklyCada lunes a las 00:00
*-*-* 02:30:00Cada día a las 02:30
*:0/15Cada 15 minutos (00, 15, 30, 45)
Mon..Fri *-*-* 09:00:00De lunes a viernes a las 09:00
Sat,Sun *-*-* 10:00:00Sábados y domingos a las 10:00
*-*-01 03:00:00El día 1 de cada mes a las 03:00
*-*-* 08,20:00:00Cada 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:

systemd-analyze calendar --iterations=3 'Mon..Fri *-*-* 09:00:00'
  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:

[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:

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:

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:

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:

mkdir -p ~/.config/systemd/user
nano ~/.config/systemd/user/limpiar-tmp.service
[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:

nano ~/.config/systemd/user/limpiar-tmp.timer
[Unit]
Description=Limpieza semanal de ~/tmp

[Timer]
OnCalendar=weekly
Persistent=true

[Install]
WantedBy=timers.target

Habilítalo:

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:

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:

crontab -l
sudo crontab -l
ls /etc/cron.d/

La traducción de las expresiones de cron más comunes es directa:

crontabOnCalendar
0 2 * * **-*-* 02:00:00
*/15 * * * **:0/15
0 9 * * 1-5Mon..Fri *-*-* 09:00:00
0 3 1 * **-*-01 03:00:00
0 0 * * 0Sun *-*-* 00:00:00
@rebootOnBootSec=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:

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.