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:
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
OnCalendarfija la hora de ejecución (todos los días a las 02:00).Persistent=trueguarda 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.RandomizedDelaySecañ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ó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:
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:
| 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 usaExecStart=/bin/sh -c '...'. - Cron carga un entorno mínimo. En un servicio puedes definir variables con
Environment=oEnvironmentFile=.
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.timerconenable --nowy que ejecutastedaemon-reloaddespués de crearlo.systemctl status nombre.timerindica 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 conUnit=otro.serviceen la sección[Timer].- El servicio falla con
status=203/EXEC: la ruta deExecStartno 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.timermuestraFailed to parse calendar specification. Valida la expresión consystemd-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.timeryman systemd.timepara todas las opciones de temporización. - Revisar los timers que ya trae Ubuntu (
apt-daily,fstrim,logrotate) como ejemplos reales.
