1. Qué agregan los temporizadores
Un temporizador de systemd (timer) es una unidad de systemd que, al llegar un momento determinado, pone en marcha otra unidad, normalmente un servicio que ejecuta la tarea. Cumple el papel de una línea de crontab, pero la tarea pasa a ser un servicio más del sistema, con todo lo que systemd sabe hacer con ellos.
systemd es el programa que arranca y administra los servicios en casi todas las distribuciones de Linux actuales. Que las tareas programadas sean servicios trae, sin escribir una línea de código, varias cosas que con cron había que resolver a mano:
| Problema con cron | Con un temporizador |
|---|---|
| Si el equipo estaba apagado a la hora prevista, la tarea no se ejecuta (tema 33). | Con Persistent=true, se ejecuta al arrancar. |
| La salida va por correo, o se pierde (tema 34). | Va al registro del sistema, con fecha y hora, y se consulta con journalctl. |
| No se sabe cómo terminó la última ejecución. | systemctl status muestra el código de salida, la duración y la hora. |
Dos ejecuciones pueden superponerse; hace falta flock (tema 27). | Si el servicio sigue en marcha, systemd no lanza otro. |
| Probar la tarea exactamente como la ejecutará cron es difícil (tema 34). | systemctl start la ejecuta ya, en su entorno real. |
| Limitar el tiempo, la memoria o la prioridad exige herramientas aparte. | Se declara en el servicio: TimeoutStartSec, MemoryMax, Nice. |
El precio es la ceremonia: donde cron necesita una línea, un temporizador necesita dos archivos pequeños. Hacia el final del tema veremos cuándo compensa cada uno.
2. Dos unidades: servicio y temporizador
systemd administra unidades, descritas en archivos de texto con un formato parecido al de los archivos de configuración del tema 30: secciones entre corchetes y líneas Clave=valor. Una tarea programada usa dos unidades con el mismo nombre y distinta extensión:
La separación tiene una ventaja práctica enorme: el servicio existe por sí mismo, y se puede ejecutar en cualquier momento sin esperar al temporizador. Y el script de la tarea no necesita saber nada de systemd: el mismo respaldar sirve con cron, con un temporizador o a mano.
Las unidades del sistema que escribe el administrador van en /etc/systemd/system/. Las que instalan los paquetes están en /usr/lib/systemd/system/, y no se tocan: si hace falta cambiar una, se copia o se complementa en /etc.
3. El servicio
[Unit]
Description=Respaldo nocturno de /home
[Service]
Type=oneshot
User=backup
ExecStart=/usr/local/bin/respaldar -q --destino /mnt/copias
Nice=10
IOSchedulingClass=idle
TimeoutStartSec=2h
Type=oneshot es el tipo que corresponde a una tarea: un proceso que hace su trabajo y termina, a diferencia de un servidor web, que queda en marcha. Un servicio de tarea no lleva sección [Install], porque no se activa por sí mismo al arrancar el sistema, sino a través de su temporizador. Estas son las directivas más útiles:
| Directiva | Efecto |
|---|---|
ExecStart= | El comando, con la ruta absoluta del programa. Puede haber varias líneas, que se ejecutan en orden. |
User=, Group= | La cuenta con que se ejecuta. Sin ellas, se ejecuta como root. |
WorkingDirectory= | El directorio de trabajo. Por omisión, /. |
Environment=, EnvironmentFile= | Variables para la tarea, escritas aquí o leídas de un archivo. |
TimeoutStartSec= | El tiempo máximo; si se supera, systemd detiene la tarea y la marca como fallida. En un oneshot, por omisión no hay límite. |
Nice=, IOSchedulingClass=idle | Menos prioridad de procesador y de disco, para no molestar a los demás. |
MemoryMax=, CPUQuota= | Límites de memoria y de procesador. |
systemd separa el comando en argumentos con sus propias reglas de comillas y lo ejecuta directamente, sin pasar por ninguna shell. No hay redirecciones, tuberías, && ni comodines: ExecStart=/usr/bin/tar czf /tmp/x.tgz /home > /tmp/log le pasa a tar un argumento >. Además, % introduce un especificador, como %n, el nombre de la unidad, y $ una variable, de modo que un % o un $ literales se escriben %% y $$. Si hace falta lógica de shell, lo correcto es un script; como recurso, ExecStart=/bin/bash -c '…'.
Antes de crear el temporizador, el servicio ya se puede probar. Después de escribir o cambiar un archivo de unidad, systemctl daemon-reload hace que systemd lo vuelva a leer:
$ sudo systemctl daemon-reload
$ sudo systemctl start respaldo.service # espera a que termine
$ systemctl status respaldo.service
○ respaldo.service - Respaldo nocturno de /home
Loaded: loaded (/etc/systemd/system/respaldo.service; static)
Active: inactive (dead) since Thu 2026-10-15 11:02:14 -03; 5s ago
Process: 53120 ExecStart=/usr/local/bin/respaldar -q --destino /mnt/copias (code=exited, status=0/SUCCESS)
Main PID: 53120 (code=exited, status=0/SUCCESS)
CPU: 1.912s
$ journalctl -u respaldo.service -n 4
oct 15 11:02:01 servidor systemd[1]: Starting respaldo.service - Respaldo nocturno de /home...
oct 15 11:02:14 servidor respaldar[53120]: Respaldo completo: 1284 archivos, 412 MiB
oct 15 11:02:14 servidor systemd[1]: respaldo.service: Deactivated successfully.
oct 15 11:02:14 servidor systemd[1]: Finished respaldo.service - Respaldo nocturno de /home.
Esto es lo que en el tema 34 costaba tanto: la tarea corrió con su usuario, su entorno y sus límites reales, y su salida quedó registrada, con el nombre del programa y su número de proceso. static indica que la unidad no se habilita por sí misma, lo esperado en un servicio que activa un temporizador. El entorno de un servicio también es reducido, y no lee ~/.bashrc, pero su PATH incluye /usr/local/bin y los directorios sbin, y no hay sorpresas: lo que funciona con systemctl start funciona igual con el temporizador.
4. El temporizador
[Unit]
Description=Respaldo nocturno de /home, de lunes a viernes
[Timer]
OnCalendar=Mon..Fri 02:30
Persistent=true
RandomizedDelaySec=10min
[Install]
WantedBy=timers.target
Por omisión, un temporizador activa el servicio de su mismo nombre: respaldo.timer activa respaldo.service. La sección [Install] sí es necesaria aquí: WantedBy=timers.target hace que, una vez habilitado, el temporizador se ponga en marcha en cada arranque.
| Directiva | Efecto |
|---|---|
OnCalendar= | Los momentos en que vence, con la sintaxis de la sección siguiente. Puede repetirse. |
Persistent=true | Recuerda la última ejecución en el disco; si al arrancar se perdió alguna mientras el equipo estaba apagado, la ejecuta enseguida. Solo tiene efecto con OnCalendar. |
RandomizedDelaySec= | Agrega un retraso al azar, hasta el valor indicado, para que muchas máquinas no lancen la tarea en el mismo segundo. |
AccuracySec= | Cuánto puede retrasarse el vencimiento para agruparlo con otros y ahorrar energía. Por omisión, un minuto; 1s si importa la precisión. |
Unit= | Activar un servicio con otro nombre. |
Para que el temporizador funcione hay que habilitarlo, para que arranque con el sistema, y ponerlo en marcha ahora. enable --now hace las dos cosas. Se habilita el temporizador, no el servicio:
$ sudo systemctl daemon-reload
$ sudo systemctl enable --now respaldo.timer
Created symlink /etc/systemd/system/timers.target.wants/respaldo.timer → /etc/systemd/system/respaldo.timer.
$ systemctl list-timers respaldo.timer
NEXT LEFT LAST PASSED UNIT ACTIVATES
Fri 2026-10-16 02:34:41 -03 15h - - respaldo.timer respaldo.service
1 timers listed.
La próxima ejecución es el viernes a las 2:34:41: las 2:30 más el retraso al azar de RandomizedDelaySec. Las columnas LAST y PASSED mostrarán la última ejecución cuando la haya.
5. Las expresiones de OnCalendar
La forma completa de una expresión es DíaDeLaSemana Año-Mes-Día Hora:Minuto:Segundo, y cada parte se puede omitir: sin día de la semana, vale cualquiera; sin fecha, todos los días; sin segundos, el segundo 0. Dentro de cada parte se usan * para cualquier valor, comas para las listas, .. para los rangos y / para los pasos.
| OnCalendar | cron | Se ejecuta |
|---|---|---|
*:0/5 | */5 * * * * | Cada 5 minutos. |
hourly | 0 * * * * | Al comienzo de cada hora. |
02:30 | 30 2 * * * | Todos los días a las 2:30. |
*-*-* 08,20:00 | 0 8,20 * * * | A las 8:00 y a las 20:00. |
Mon..Fri 02:30 | 30 2 * * 1-5 | De lunes a viernes a las 2:30. |
Sat,Sun 10:00 | 0 10 * * 6,0 | Sábados y domingos a las 10. |
*-*-01 08:00 | 0 8 1 * * | El día 1 de cada mes a las 8. |
*-01/3-01 03:00 | 0 3 1 */3 * | El 1 de enero, abril, julio y octubre. |
Mon *-*-01..07 09:00 | — | El primer lunes de cada mes. |
*-*~01 23:00 | — | El último día de cada mes. |
daily, weekly, monthly | @daily… | A medianoche: cada día, cada lunes, cada día 1. |
Hay dos diferencias con cron que conviene fijar. La primera resuelve el problema de la sección 6 del tema 33: en systemd, el día de la semana y la fecha se combinan con «y», como el resto, de modo que Mon *-*-01..07 es exactamente «un lunes entre el 1 y el 7», y Fri *-*-13, los viernes 13; la tilde cuenta los días desde el final del mes, de modo que ~01 es el último. La segunda es una trampa: weekly vence los lunes, mientras que @weekly de cron, los domingos.
No hace falta adivinar si una expresión dice lo que uno cree: systemd-analyze calendar la interpreta, la muestra en su forma completa y calcula los próximos vencimientos.
$ systemd-analyze calendar 'Mon..Fri 02:30'
Original form: Mon..Fri 02:30
Normalized form: Mon..Fri *-*-* 02:30:00
Next elapse: Fri 2026-10-16 02:30:00 -03
(in UTC): Fri 2026-10-16 05:30:00 UTC
From now: 15h left
$ systemd-analyze calendar --iterations=3 'Mon *-*-01..07 09:00'
Original form: Mon *-*-01..07 09:00
Normalized form: Mon *-*-01..07 09:00:00
Next elapse: Mon 2026-11-02 09:00:00 -03
…
Iteration #2: Mon 2026-12-07 09:00:00 -03
…
Iteration #3: Mon 2027-01-04 09:00:00 -03
…
$ systemd-analyze calendar '*/5 * * * *'
Failed to parse calendar specification '*/5 * * * *': Invalid argument
La última línea muestra el error más común al pasar de cron a systemd: copiar la expresión de cron, que systemd no entiende. Al final de una expresión puede ir además una zona horaria, como 02:30 UTC, que evita las sorpresas del horario de verano que vimos en el tema 33.
6. Temporizadores relativos
Además de las fechas del calendario, un temporizador puede vencer un tiempo después de un acontecimiento. Son los temporizadores monotónicos, que no dependen del reloj sino del tiempo transcurrido:
| Directiva | Vence |
|---|---|
OnBootSec= | Un tiempo después del arranque del sistema. |
OnActiveSec= | Un tiempo después de que se activó el propio temporizador. |
OnUnitActiveSec= | Un tiempo después de la última vez que empezó el servicio. |
OnUnitInactiveSec= | Un tiempo después de la última vez que terminó el servicio. |
[Timer]
OnBootSec=2min
OnUnitInactiveSec=10min
[Install]
WantedBy=timers.target
Esta combinación expresa algo que cron no puede: «dos minutos después de arrancar, y luego diez minutos después de que termine cada ejecución». Si una sincronización tarda doce minutos, la siguiente empieza diez minutos después de que termine, nunca encima. Los tiempos se escriben con unidades, como 30s, 10min, 2h o 1d, y se pueden combinar: 1h 30min.
7. Vigilar y avisar de los fallos
Una vez instalados, los temporizadores se vigilan con unos pocos comandos:
| Comando | Muestra |
|---|---|
systemctl list-timers | Todos los temporizadores activos, con su próximo y su último vencimiento. --all incluye los inactivos. |
systemctl status respaldo.service | Cómo terminó la última ejecución, con su código y su duración. |
journalctl -u respaldo.service | La salida de todas las ejecuciones; --since yesterday, -n 50 o -f la acotan o la siguen en vivo. |
systemctl --failed | Las unidades cuya última ejecución falló. |
sudo systemctl reset-failed respaldo.service | Borra la marca de fallo, una vez resuelto el problema. |
Si la tarea termina con un código distinto de cero, el servicio queda marcado como failed. Para enterarse sin tener que mirar, la directiva OnFailure= pone en marcha otra unidad cuando la tarea falla. Lo habitual es escribir una sola unidad de aviso, en forma de plantilla, que sirva para todas las tareas:
[Unit]
Description=Aviso de fallo de %i
[Service]
Type=oneshot
ExecStart=/usr/local/bin/avisar "Falló %i en %H"
OnFailure=aviso-fallo@%n.service
El @ del nombre indica una plantilla: aviso-fallo@respaldo.service.service es una instancia de ella, en la que %i vale lo que va entre la arroba y la extensión. En respaldo.service, %n es su propio nombre, y en el aviso %H es el nombre del equipo. avisar es un script propio, que puede enviar un correo, un mensaje a un servicio de chat o lo que convenga, y que sirve a todas las tareas a la vez. Son los especificadores con % que obligaban a escribir %% en ExecStart.
8. Temporizadores de usuario
Como cada usuario tiene su crontab, cada usuario tiene también su propio systemd, que administra sus unidades sin necesitar root. Funcionan igual, con tres diferencias: los archivos van en ~/.config/systemd/user/, todos los comandos llevan --user, y se ejecutan siempre con la cuenta del usuario, de modo que no llevan User=.
$ ls ~/.config/systemd/user/
limpiar-descargas.service limpiar-descargas.timer
$ systemctl --user daemon-reload
$ systemctl --user enable --now limpiar-descargas.timer
$ journalctl --user -u limpiar-descargas.service
$ sudo loginctl enable-linger ana # que funcione sin sesión abierta
La última línea resuelve una sorpresa: el systemd de un usuario se pone en marcha cuando inicia sesión y se detiene cuando cierra la última, y con él sus temporizadores. En un equipo personal, donde uno está conectado casi siempre, puede alcanzar; en un servidor, donde nadie tiene una sesión abierta a las 2:30, las tareas no correrían. loginctl enable-linger mantiene el systemd del usuario en marcha desde el arranque, con o sin sesión.
9. Temporizadores sin archivos: systemd-run
Para una tarea ocasional, escribir dos archivos es demasiado. systemd-run crea un servicio y un temporizador transitorios, que existen hasta que la tarea se ejecuta o hasta el próximo reinicio, y que tienen las mismas ventajas: registro, estado y límites.
$ systemd-run --user --on-active=2h /home/ana/bin/recordatorio "Llamar al banco"
Running timer as unit: run-r8c1f0b5e2d4a4f9b.timer
Will run service as unit: run-r8c1f0b5e2d4a4f9b.service
$ systemd-run --user --on-calendar='2026-10-20 09:00' /home/ana/bin/informe-mensual
$ systemctl --user list-timers 'run-*'
$ systemd-run --user --wait --pipe -p MemoryMax=1G /home/ana/bin/procesar-videos
Sin opciones de tiempo, systemd-run ejecuta el comando enseguida como un servicio, lo que también es útil: la última línea ejecuta un proceso con un límite de memoria, espera a que termine y muestra su salida en la terminal. Con root y sin --user, crea unidades del sistema.
10. cron o systemd
Ninguna de las dos herramientas reemplaza del todo a la otra, y en la práctica conviven:
| Aspecto | cron | Temporizadores de systemd |
|---|---|---|
| Escribir una tarea | Una línea. | Dos archivos. |
| Disponibilidad | Cualquier Unix, contenedores, sistemas mínimos. | Solo sistemas con systemd. |
| Expresiones de tiempo | Universales, pero con la regla del «o» y sin segundos. | Más expresivas, y comprobables con systemd-analyze. |
| Ejecuciones perdidas | No se recuperan. | Persistent=true. |
| Salida y estado | Correo; el código se pierde. | En el registro, con el código y la duración. |
| Superposición | Hace falta flock. | Evitada de fábrica. |
| Límites de recursos | Herramientas aparte. | Directivas del servicio. |
| Probar la tarea | Reproducir el entorno a mano. | systemctl start. |
Una regla razonable: para una tarea personal rápida o un sistema sin systemd, cron; para tareas de servidor que importan, como respaldos, sincronizaciones o limpiezas, de las que hay que saber si fallaron, un temporizador. Los scripts son los mismos en los dos casos, y pasar de uno a otro es cuestión de minutos.
11. Tareas de una sola vez: at
cron y los temporizadores repiten. Para ejecutar algo una sola vez en el futuro, como reiniciar un servicio esta noche, lanzar una compilación larga cuando termine la jornada o recordarse algo dentro de una hora, existe at. No siempre viene instalado: está en el paquete at, y necesita su servicio, atd, en marcha.
at recibe un momento como argumento, y los comandos por la entrada estándar, interactivamente o por una tubería:
$ at 23:00
warning: commands will be executed using /bin/sh
at> /home/ana/bin/compilar-todo > /tmp/compilacion.log 2>&1
at> <EOT> # Ctrl+D para terminar
job 7 at Thu Oct 15 23:00:00 2026
$ echo '/home/ana/bin/recordatorio "Llamar al banco"' | at now + 1 hour
job 8 at Thu Oct 15 12:15:00 2026
$ at -f /home/ana/bin/reiniciar-app 03:00 tomorrow
job 9 at Fri Oct 16 03:00:00 2026
$ atq
7 Thu Oct 15 23:00:00 2026 a ana
8 Thu Oct 15 12:15:00 2026 a ana
9 Fri Oct 16 03:00:00 2026 a ana
$ atrm 8
| Forma | Ejemplo |
|---|---|
| Una hora, hoy o mañana si ya pasó | at 23:00, at 6pm |
| Palabras | at noon, at midnight, at now |
| Desde ahora | at now + 30 minutes, at now + 2 hours, at now + 3 days |
| Una fecha | at 09:00 2026-10-20, at 09:00 tomorrow, at 10:00 next monday |
Los comandos complementarios son atq, que lista las tareas pendientes, atrm, que borra una por su número, y at -c número, que muestra el script completo que se ejecutará. Ese script revela la diferencia más importante con cron: at guarda el entorno y el directorio de trabajo del momento en que se programó la tarea, y los restaura al ejecutarla. Lo que funciona en tu terminal tiene muchas más posibilidades de funcionar con at que con cron.
$ at -c 7
#!/bin/sh
# atrun uid=1000 gid=1000
…
PATH=/home/ana/bin:/usr/local/bin:/usr/bin:/bin; export PATH
LANG=es_AR.UTF-8; export LANG
…
cd /home/ana/proyectos/app || {
echo 'Execution directory inaccessible' >&2
exit 1
}
/home/ana/bin/compilar-todo > /tmp/compilacion.log 2>&1
Igual que cron, at usa /bin/sh y envía la salida por correo, que sin servicio de correo se pierde; por eso el primer ejemplo la redirige. Su pariente batch recibe los comandos de la misma forma, pero no los ejecuta a una hora sino cuando la carga del sistema baja, útil para trabajos pesados que pueden esperar. Y la alternativa con systemd es la de la sección 9: systemd-run --on-active=1h o --on-calendar, con la salida en el registro.
12. Un script completo
En el tema 33, cron-tarea agregaba y quitaba líneas del crontab sin duplicarlas. Este script hace lo mismo con temporizadores de usuario: escribe la pareja de unidades, las habilita y las quita, y deja todo como estaba si se ejecuta dos veces con los mismos datos. Escribe los dos archivos de cada tarea a partir de un nombre, un calendario y un comando, y resuelve los dos detalles más incómodos del formato: las comillas y los caracteres especiales de ExecStart.
#!/usr/bin/env bash
#
# timer-tarea — crea, cambia y quita temporizadores de systemd del usuario:
# el equivalente de cron-tarea (tema 33) para systemd.
#
# Uso: timer-tarea poner NOMBRE 'CALENDARIO' PROGRAMA [ARGUMENTO...]
# timer-tarea quitar NOMBRE
# timer-tarea listar
#
set -euo pipefail
prog="${0##*/}"
dir="${XDG_CONFIG_HOME:-$HOME/.config}/systemd/user"
uso() {
echo "Uso: $prog poner NOMBRE 'CALENDARIO' PROGRAMA [ARGUMENTO...]" >&2
echo " $prog quitar NOMBRE" >&2
echo " $prog listar" >&2
exit 2
}
error() { echo "$prog: $*" >&2; exit 1; }
escapar() { # un argumento, entre comillas y con los escapes de systemd
local a="$1"
a="${a//'\'/'\\'}"
a="${a//'"'/'\"'}"
a="${a//'%'/'%%'}" # % introduce un especificador
a="${a//'$'/'$$'}" # $ introduce una variable
printf '"%s"' "$a"
}
actualizar() { # escribe $2 en el archivo $1, solo si cambia
local archivo="$1" contenido="$2"
if [[ -f "$archivo" && "$(< "$archivo")" == "$contenido" ]]; then
return 1 # ya estaba así
fi
printf '%s\n' "$contenido" > "$archivo.tmp"
mv -f -- "$archivo.tmp" "$archivo"
}
(( $# >= 1 )) || uso
accion="$1"; shift
case "$accion" in
poner)
(( $# >= 3 )) || uso
nombre="$1" calendario="$2"; shift 2
[[ "$nombre" =~ ^[a-z0-9][a-z0-9-]*$ ]] || error "nombre no válido: $nombre"
[[ "$calendario" != *$'\n'* ]] && systemd-analyze calendar "$calendario" &> /dev/null ||
error "calendario no válido: $calendario"
programa=$(command -v -- "$1") && [[ "$programa" == /* ]] ||
error "no se encuentra el programa: $1"
shift
linea=$(escapar "$programa")
for argumento in "$@"; do
[[ "$argumento" != *$'\n'* ]] || error "un argumento no puede tener saltos de línea"
linea+=" $(escapar "$argumento")"
done
unidad="tarea-$nombre"
mkdir -p "$dir"
cambios=0
actualizar "$dir/$unidad.service" "[Unit]
Description=Tarea $nombre (timer-tarea)
[Service]
Type=oneshot
ExecStart=$linea" && cambios=1
actualizar "$dir/$unidad.timer" "[Unit]
Description=Temporizador de la tarea $nombre (timer-tarea)
[Timer]
OnCalendar=$calendario
Persistent=true
[Install]
WantedBy=timers.target" && cambios=1
if (( cambios )); then
systemctl --user daemon-reload
systemctl --user enable --quiet "$unidad.timer"
systemctl --user restart "$unidad.timer"
echo "Tarea $nombre programada." >&2
else
systemctl --user enable --quiet --now "$unidad.timer"
echo "La tarea $nombre ya estaba programada así; no hay cambios." >&2
fi
usuario="${USER:-$(id -un)}"
if [[ "$(loginctl show-user "$usuario" --property=Linger --value 2> /dev/null)" != yes ]]; then
echo "Aviso: la tarea solo correrá mientras tengas una sesión abierta;" >&2
echo " para evitarlo: sudo loginctl enable-linger $usuario" >&2
fi
systemctl --user list-timers --no-pager "$unidad.timer"
;;
quitar)
(( $# == 1 )) || uso
unidad="tarea-$1"
if [[ ! -e "$dir/$unidad.timer" && ! -e "$dir/$unidad.service" ]]; then
echo "No había ninguna tarea llamada $1; no hay cambios." >&2
exit 0
fi
systemctl --user disable --quiet --now "$unidad.timer" 2> /dev/null || true
rm -f -- "$dir/$unidad.timer" "$dir/$unidad.service"
systemctl --user daemon-reload
echo "Tarea $1 quitada." >&2
;;
listar)
systemctl --user list-timers --all --no-pager 'tarea-*'
;;
*) uso ;;
esac
$ timer-tarea poner respaldo 'Mon..Fri 02:30' respaldar -q --destino '/mnt/copias/casa de ana'
Tarea respaldo programada.
Aviso: la tarea solo correrá mientras tengas una sesión abierta;
para evitarlo: sudo loginctl enable-linger ana
NEXT LEFT LAST PASSED UNIT ACTIVATES
Fri 2026-10-16 02:30:00 -03 15h - - tarea-respaldo.timer tarea-respaldo.service
1 timers listed.
$ sudo loginctl enable-linger ana
$ timer-tarea poner respaldo 'Mon..Fri 02:30' respaldar -q --destino '/mnt/copias/casa de ana'
La tarea respaldo ya estaba programada así; no hay cambios.
NEXT LEFT LAST PASSED UNIT ACTIVATES
Fri 2026-10-16 02:30:00 -03 15h - - tarea-respaldo.timer tarea-respaldo.service
1 timers listed.
$ cat ~/.config/systemd/user/tarea-respaldo.service
[Unit]
Description=Tarea respaldo (timer-tarea)
[Service]
Type=oneshot
ExecStart="/home/ana/bin/respaldar" "-q" "--destino" "/mnt/copias/casa de ana"
$ timer-tarea poner limpiar '*/5 * * * *' limpiar-temporales
timer-tarea: calendario no válido: */5 * * * *
$ systemctl --user start tarea-respaldo.service # probarla ya
$ journalctl --user -u tarea-respaldo.service -n 1 -o cat
Finished tarea-respaldo.service - Tarea respaldo (timer-tarea).
$ timer-tarea quitar respaldo
Tarea respaldo quitada.
- Un archivo por unidad, en lugar de una línea por tarea: las tareas se reconocen por su nombre,
tarea-NOMBRE, y el prefijo evita chocar con las unidades que el usuario escribió a mano. Por esolistarpuede usar el patrón'tarea-*'delist-timers. - La validación del calendario es la de systemd: en lugar de una expresión regular aproximada, como en
cron-tarea, el script le pregunta asystemd-analyze calendar, que usa el mismo intérprete que el temporizador. Si lo acepta, el temporizador también lo aceptará. - El programa, con su ruta absoluta:
command -vlo busca en elPATHde quien ejecuta el script, que es el de la terminal, y la ruta completa queda escrita en la unidad. Así elPATHdel servicio deja de importar, y la comprobación== /*rechaza las órdenes internas y los alias, comocd, quecommand -vdevuelve sin ruta. - Cada argumento se escapa para systemd: va entre comillas dobles, con la barra invertida y las comillas protegidas, el
%duplicado para que no se tome como especificador y el$duplicado para que no se tome como variable. Es el mismo cuidado que el\%decron-tarea, con las reglas de otro formato. Como en ese script, las comillas simples en los reemplazos hacen que cada texto se lea tal cual. - Escribe solo lo que cambia:
actualizarcompara el contenido nuevo con el del archivo y, si son iguales, no lo toca. Cuando escribe, lo hace en un temporal y lo mueve, la escritura atómica del tema 24, para que systemd nunca lea un archivo a medias. - Lleva la tarea al estado deseado, como proponía el tema 27: si algo cambió, recarga systemd y reinicia el temporizador para que calcule de nuevo el próximo vencimiento; si nada cambió, igual se asegura de que esté habilitado y en marcha, por si alguien lo detuvo a mano.
- Avisa del problema del linger, el error más fácil de cometer con los temporizadores de usuario y el más difícil de descubrir, porque todo parece funcionar mientras uno está conectado.
13. Errores frecuentes
| Escribiste | Qué pasa | Corrección |
|---|---|---|
| Cambiar un archivo de unidad y nada más | systemd sigue usando la versión anterior, y lo advierte en status. | systemctl daemon-reload |
systemctl enable respaldo.timer | Queda habilitado para el próximo arranque, pero no corre hasta entonces. | enable --now |
systemctl enable respaldo.service | Se intentó habilitar el servicio en lugar del temporizador: como no tiene [Install], systemd lo advierte y no habilita nada. | Habilitar el .timer. |
OnCalendar=*/5 * * * * | Es sintaxis de cron: el temporizador no carga. | OnCalendar=*:0/5, comprobado con systemd-analyze calendar. |
OnCalendar=weekly esperando el domingo | En systemd, weekly es el lunes. | OnCalendar=Sun 00:00 |
ExecStart=… > /tmp/log o con | | No hay shell: > y | llegan como argumentos. | Un script, o /bin/bash -c '…'. |
date +%F en ExecStart | %F es un especificador de systemd. | %%F, o llevarlo a un script. |
| Un temporizador de usuario en un servidor | Solo corre mientras el usuario tiene una sesión abierta. | sudo loginctl enable-linger usuario |
Persistent=true con OnBootSec | No tiene efecto: solo vale con OnCalendar. | Usar OnCalendar si hay que recuperar ejecuciones. |
at 23:00 sin atd en marcha | Can't open /var/run/atd.pid to signal atd. No atd running? | Instalar at y systemctl enable --now atd. |
Una tarea de at sin redirigir la salida | Sin correo configurado, la salida se pierde. | > archivo 2>&1, o systemd-run --on-active. |
14. Resumen
- Un temporizador de systemd pone en marcha un servicio en un momento dado; la tarea gana registro, estado, ejecuciones recuperadas, protección contra la superposición y límites de recursos.
- Cada tarea usa dos unidades:
NOMBRE.service, conType=oneshotyExecStart, yNOMBRE.timer, conOnCalendar,Persistent=trueyWantedBy=timers.target. ExecStartno pasa por una shell;%y$literales se escriben%%y$$.- Después de cambiar una unidad,
daemon-reload; para activar un temporizador,enable --nowsobre el.timer; para probar la tarea,systemctl startsobre el.service. OnCalendarusa la formaDíaSemana Año-Mes-Día Hora:Min:Seg, combina siempre con «y», y se comprueba consystemd-analyze calendar.OnBootSec,OnUnitActiveSecyOnUnitInactiveSecvencen un tiempo después de un acontecimiento.list-timers,status,journalctl -uy--failedvigilan las tareas;OnFailure=avisa cuando una falla.- Los temporizadores de usuario van en
~/.config/systemd/user/, se manejan con--usery necesitanloginctl enable-lingerpara correr sin sesión. systemd-run --on-activeo--on-calendarcrea un temporizador transitorio sin escribir archivos.atejecuta comandos una sola vez, conservando el entorno y el directorio;atq,atrmyat -clos administran, ybatchespera a que baje la carga.
15. Preguntas de repaso
Intenta responder cada una antes de abrir la respuesta.
1. ¿Por qué una tarea programada con systemd usa dos unidades y no una?
Porque separan dos responsabilidades: el temporizador decide cuándo, y el servicio, qué se ejecuta y en qué condiciones. Así el servicio puede ejecutarse a mano con systemctl start, exactamente como lo hará el temporizador.
2. ¿Qué tipo de servicio corresponde a una tarea programada, y por qué no lleva sección [Install]?
Type=oneshot, porque la tarea hace su trabajo y termina. No lleva [Install] porque no se habilita por sí misma: la pone en marcha el temporizador, que es el que se habilita.
3. ¿Qué hace Persistent=true?
Hace que systemd recuerde en el disco la última ejecución. Si al arrancar descubre que se perdió alguna mientras el equipo estaba apagado, ejecuta la tarea enseguida. Solo funciona con OnCalendar.
4. ¿Por qué ExecStart=/usr/bin/echo hola > /tmp/saludo no crea el archivo?
Porque systemd ejecuta el comando directamente, sin una shell que interprete la redirección: echo recibe los argumentos hola, > y /tmp/saludo, y los escribe en el registro.
5. ¿Cómo se escribe en OnCalendar «el primer lunes de cada mes a las 9», y por qué en cron no es tan simple?
Mon *-*-01..07 09:00. En systemd, el día de la semana y la fecha se combinan con «y»; en cron, si los dos campos tienen valores, basta con que coincida uno, de modo que hay que agregar una condición en el comando.
6. ¿Cómo se comprueba una expresión de OnCalendar antes de usarla?
Con systemd-analyze calendar 'expresión', que la muestra en su forma completa y calcula el próximo vencimiento; con --iterations=N, los N próximos. Si la expresión no es válida, lo indica.
7. ¿Qué diferencia hay entre OnUnitActiveSec=10min y OnUnitInactiveSec=10min?
La primera cuenta los 10 minutos desde que empezó la última ejecución; la segunda, desde que terminó. Con la segunda, una ejecución larga nunca deja menos de 10 minutos de descanso hasta la siguiente.
8. Un temporizador de usuario funciona en tu portátil pero no en el servidor. ¿Cuál es la causa más probable?
Que el systemd del usuario solo está en marcha mientras tiene una sesión abierta, y en el servidor nadie está conectado a la hora de la tarea. Se resuelve con sudo loginctl enable-linger usuario.
9. ¿En qué se diferencia el entorno de una tarea de at del de una de cron?
at guarda las variables de entorno y el directorio de trabajo del momento en que se programó la tarea, y los restaura al ejecutarla. Cron arranca siempre con un entorno mínimo y en el directorio personal.
10. ¿Por qué timer-tarea escribe %% y $$ en ExecStart?
Porque en las unidades de systemd % introduce un especificador, como %n, y $ una variable. Duplicarlos hace que el programa reciba el carácter literal que el usuario escribió.
Para practicar
- Convierte a
OnCalendarlas expresiones de cron que escribiste en el tema 33, y comprueba cada una consystemd-analyze calendar --iterations=3. - Crea un temporizador de usuario que cada 15 minutos escriba la fecha en un archivo. Ejecuta el servicio a mano con
systemctl --user start, revisajournalctl --user -uylist-timers, y luego quítalo. - Haz que la tarea anterior falle a propósito, con
exit 1, y observa cómo aparece ensystemctl --user statusy ensystemctl --user --failed. - Escribe una plantilla
aviso-fallo@.serviceque registre el fallo conlogger, y conéctala a la tarea conOnFailure=. - Programa con
atun comando para dentro de dos minutos, mira el script que guardó conat -c, y compara el resultado con el mismo comando programado consystemd-run --user --on-active=2min. - Agrega a
timer-tareauna opción-r MINUTOSque escribaRandomizedDelaySecen el temporizador, y otra-t SEGUNDOSque escribaTimeoutStartSecen el servicio.