- Los temporizadores de systemd son un reemplazo práctico de
cron: ejecutan unidades como .service según una programación y hacen más clara la gestión del historial, la salida y el entorno
- El
cron tradicional tiene como puntos débiles un $PATH ambiguo, stdout/stderr que se pierde con facilidad, un historial de ejecución difícil de rastrear y una sintaxis de programación complicada de leer
- Los temporizadores enlazan un
.timer y un .service con la misma raíz de nombre, y expresan ejecuciones basadas en hora o eventos con OnCalendar, OnBootSec y OnUnitActiveSec
- Con
systemd-analyze calendar y systemctl list-timers se puede revisar la expresión de tiempo y la próxima hora de ejecución, y WakeSystem= puede reactivar la ejecución incluso desde suspensión
RandomizedOffsetSec y FixedRandomDelay= reducen los picos por ejecuciones simultáneas, y Persistent= compensa justo después de volver en línea las ejecuciones perdidas mientras estuvo inactivo
Por qué usar temporizadores de systemd como reemplazo de cron
- El término
cron job se usa ampliamente para referirse al componente básico de cómputo que ejecuta tareas según una programación, como “ejecuta esto todos los días” o “ejecuta aquello cada mes”, incluso cuando no se trata del demonio cron real
- Un temporizador de systemd es una unidad de systemd que ejecuta otra unidad, normalmente una
.service, siguiendo una programación específica, y puede ser un reemplazo funcional del demonio cron tradicional
- El
cron tradicional tiene varias debilidades prácticas
- La configuración ambigua de
$PATH hace difícil predecir el resultado de ejecutar scripts
- La salida de
stdout y stderr muchas veces termina en un agujero negro o se envía al sistema de correo del host
- Es difícil rastrear y consultar el historial de ejecuciones
- Una sintaxis de programación como
01,31 04,05 1-15 1,6 * no es fácil de leer ni intuitiva para las personas
- Los temporizadores de systemd reducen estos problemas y al mismo tiempo ofrecen una configuración de calendario parecida a las expresiones estilo
cron
Estructura básica: servicio y temporizador
- Un temporizador de systemd necesita un objetivo de ejecución, y una unidad
.service puede verse lógicamente como si fuera un script
- Por ejemplo, si se coloca la siguiente unidad en
/etc/systemd/system/roulette.service, se instala un servicio que apaga la computadora con una probabilidad de 1 entre 10
[Unit]
Description=1 in 10 chance to break your chains
[Service]
ExecStart=/usr/bin/env bash -c '[[ $(($RANDOM % 10)) == 0 ]] && systemctl poweroff || echo LIVE ANOTHER DAY'
ExecCondition= es una forma más integrada de expresar ejecución condicional como opción de servicio de systemd, y deja más claro a nivel de unidad si “¿debe continuar la ejecución?”
[Unit]
Description=1 in 10 chance to break your chains
[Service]
ExecCondition=/run/current-system/sw/bin/bash -c '[[ $(($RANDOM % 10)) == 0 ]]'
ExecStart=/run/current-system/sw/bin/systemctl poweroff
- Si la condición no se cumple, queda un mensaje más claro en el journal
May 05 11:05:32 diesel systemd[3117]: Condition check resulted in 1 in 10 chance to break your chains being skipped.
- En general, aprovechar las opciones que ofrece systemd da una mejor experiencia que hacer todo manualmente con scripts
OnFailure= puede usarse para reaccionar cuando falla un script de servicio
Restart= puede usarse para intentar recuperarse de fallas transitorias
Enlace y ejecución de unidades temporizador
- Si se coloca
/etc/systemd/system/roulette.timer con la misma raíz de nombre, se puede enlazar el temporizador con roulette.service
[Unit]
Description=impending destruction
[Timer]
OnCalendar=10:00
[Install]
WantedBy=timers.target
- Por defecto, la opción
Unit= del temporizador selecciona la unidad de servicio con la misma raíz de nombre y sufijo .service
- En este ejemplo, se selecciona
roulette.service
- Si se quiere ejecutar una unidad de servicio con otro nombre, se puede cambiar
Unit=
- El destino de
ExecStart= no se ejecuta como comando de shell por defecto
- Un destino con ruta absoluta debe tratarse como un script o como un intérprete que espera un script en forma de argumento de cadena
ExecStart=/usr/bin/echo Hello | /usr/bin/awk no funciona en este contexto porque la tubería no tiene significado aquí
- Los argumentos de
ExecStart= no heredan variables de entorno por defecto, salvo algunos valores predeterminados del administrador del sistema
- El
$PATH predeterminado está casi vacío
- Ejecutar
/usr/bin/env es una protección simple para poder usar elementos como systemctl
- Si solo se hubiera usado
ExecStart=/usr/bin/bash, $PATH sí incluiría entradas predeterminadas, pero usar env añade una capa extra de seguridad
- El servicio puede ejecutarse directamente sin temporizador
systemctl start roulette
- Un servicio sin sección
[Install] no puede activarse con enable, y en esta estructura el temporizador es la forma estándar de ejecutar el servicio de manera consistente
systemctl actúa sobre roulette.service por defecto aunque no se escriba el sufijo explícito
- Si se aplica
systemctl start a una unidad .timer, el temporizador queda activo, pero no ejecuta de inmediato el servicio objetivo real de Unit=
systemctl start roulette.timer
status muestra cuándo será la siguiente ejecución del temporizador
systemctl status roulette.timer
Trigger: Sat 2026-04-18 10:00:00 MDT; 35min left
- El flujo más simple consiste en crear el servicio objetivo, poner un temporizador con programación en la misma ubicación e iniciar el temporizador, no el objetivo
- Si la sección
[Install] del temporizador incluye WantedBy=, también se puede hacer que el temporizador se levante al arrancar el sistema
systemctl enable roulette.timer
Expresiones de tiempo: eventos de calendario y duraciones
- En los temporizadores importa cómo se expresa la programación, y conviene distinguir entre intervalos repetitivos y eventos de calendario o marcas de tiempo
- La página del manual
systemd.time(7) tiene suficientes ejemplos y vale como primer material de referencia al escribir temporizadores
systemd-analyze puede validar y explicar expresiones de tiempo
systemd-analyze calendar '*-*-* *:*:*'
Normalized form: *-*-* *:*:*
Next elapse: Sat 2026-04-18 16:44:26 MDT
(in UTC): Sat 2026-04-18 22:44:26 UTC
From now: 431ms left
- Los temporizadores de systemd pueden definir no solo horas repetitivas basadas en reloj de pared, sino también duraciones repetidas a partir de algún evento previo, a diferencia del
cron tradicional
- La forma completa de
daily significa ejecutar todos los años, todos los meses y todos los días a las 00:00:00
*-*-* 00:00:00
│ │ │ │ │ ╰── at second 00
│ │ │ │ ╰───── at minute 00
│ │ │ ╰──────── at hour 00
│ │ ╰────────── every day
│ ╰──────────── every month
╰────────────── every year
- Se pueden usar abreviaturas como
daily, la forma completa y otros valores soportados por systemd.time(7), y systemd-analyze sirve para comprobar los supuestos
Cuando conviene más una ejecución basada en eventos
- En tareas reales, muchas veces encaja mejor “ejecutar después de otro evento” que “ejecutar todos los días a la misma hora”
- Si una tarea vacía un directorio temporal, puede que haya muy poco que limpiar en
/tmp si la expresión de cron ya pasó justo después del arranque
- Expresarlo como “ejecuta una hora después de que arranque la computadora y luego cada hora” se alinea mejor con el comportamiento real del servicio y la lógica del calendario
[Timer]
OnBootSec=1h
OnUnitActiveSec=1h
OnBootSec=1h significa ejecutar una vez una hora después de iniciar la máquina
OnUnitActiveSec=1h significa volver a ejecutar una hora después de que se haya ejecutado Unit=, lo que hace que el temporizador se repita implícitamente
- Este tipo de expresión periódica por duración suele encajar mejor para tareas de “ejecutar de vez en cuando” que expresiones como “ejecuta en este minuto de cada hora”
- En el ejemplo de un bot de Slack que consulta la API de Advent of Code, la expresión
*/15 de cron cumple la política de la API de “cada 15 minutos”, pero si todos consultan igual, el tráfico puede concentrarse
- Si tras modificar el código se inicia el temporizador y luego se ejecuta cada 15 minutos, se cumple el comportamiento necesario y además puede reducirse el problema de thundering herd
Ver el estado de los temporizadores de un vistazo
systemctl list-timers es un comando de alto nivel que resume el estado de los temporizadores en una máquina
systemctl list-timers
NEXT LEFT LAST PASSED UNIT ACTIVATES
Mon 2026-04-20 15:15:00 MDT 1min 40s Mon 2026-04-20 15:00:05 MDT 13min ago zfs-snapshot-frequent.timer zfs-snapshot-frequent.service
Mon 2026-04-20 15:32:16 MDT 18min Mon 2026-04-20 14:22:15 MDT 51min ago fwupd-refresh.timer fwupd-refresh.service
Mon 2026-04-20 16:00:00 MDT 46min Mon 2026-04-20 15:00:05 MDT 13min ago logrotate.timer logrotate.service
Mon 2026-04-20 16:00:00 MDT 46min Mon 2026-04-20 15:00:05 MDT 13min ago zfs-snapshot-hourly.timer zfs-snapshot-hourly.service
Tue 2026-04-21 00:00:00 MDT 8h Mon 2026-04-20 09:43:22 MDT 5h 29min ago zfs-snapshot-daily.timer zfs-snapshot-daily.service
Tue 2026-04-21 07:31:28 MDT 16h Sun 2026-04-19 20:15:47 MDT 7h ago systemd-tmpfiles-clean.timer systemd-tmpfiles-clean.service
Mon 2026-04-27 00:00:00 MDT 6 days Mon 2026-04-20 09:43:22 MDT 5h 29min ago zfs-snapshot-weekly.timer zfs-snapshot-weekly.service
Mon 2026-04-27 01:09:27 MDT 6 days Mon 2026-04-20 09:43:22 MDT 5h 29min ago fstrim.timer fstrim.service
Mon 2026-04-27 04:28:38 MDT 6 days Mon 2026-04-20 09:43:22 MDT 5h 29min ago zpool-trim.timer zpool-trim.service
Fri 2026-05-01 00:00:00 MDT 1 week 3 days Wed 2026-04-01 10:07:51 MDT 1 week 1 day ago zfs-snapshot-monthly.timer zfs-snapshot-monthly.service
Fri 2026-05-01 03:17:17 MDT 1 week 3 days Wed 2026-04-01 10:07:51 MDT 1 week 1 day ago zfs-scrub.timer zfs-scrub.service
11 timers listed.
Pass --all to see loaded but inactive timers, too.
- Con un solo comando se obtiene una vista completa de lo que se ejecuta según la programación de temporizadores
list-timers forma parte de la familia de subcomandos de systemd que se usan con frecuencia
list-units también es útil
list-paths es un subcomando agregado más recientemente a systemctl
Reactivar y ejecutar desde suspensión
WakeSystem= puede hacer que un temporizador cuyo tiempo se cumpla reactive el sistema desde suspensión
WakeSystem=
Takes a boolean argument. If true, an elapsing timer will
cause the system to resume from suspend, should it be
suspended and if the system supports this.
...
- Esta función es útil cuando hay que ejecutar un script importante sin depender de la acción física de que una persona abra la tapa de la laptop
- En distribuciones como Arch o NixOS, que permiten descargar actualizaciones de paquetes antes de usarlas, se pueden traer los paquetes durante la noche y actualizarlos por la mañana ya frente al teclado
- El manual indica que, si se quiere volver a suspender el sistema después de que termine la
.service, hay que hacerlo manualmente
Distribuir la hora de ejecución y mitigar el thundering herd
- El problema de thundering herd es un problema del sistema que ocurre cuando muchos procesos despiertan al mismo tiempo
- Si todos los sistemas Debian del mundo tuvieran
apt update fijado a las 00:00:00, la medianoche sería un pésimo pico de tráfico para todos
FixedRandomDelay= y RandomizedOffsetSec= ayudan a distribuir los tiempos de ejecución
FixedRandomDelay=
Takes a boolean argument. When enabled, the randomized delay
specified by RandomizedDelaySec= is chosen deterministically,
and remains stable between all firings of the same timer,
even if the manager is restarted. ...
RandomizedOffsetSec=
Offsets the timer by a stable, randomly-selected, and evenly
distributed amount of time between 0 and the specified time
value. ...
- Estas opciones pueden usarse en sistemas reales que revisan actualizaciones de software
- Repartir las ejecuciones con distribución uniforme ayuda a reducir el problema de thundering herd, mantener un comportamiento consistente y evitar interferencias como reinicios de demonios mientras se coordinan servicios distribuidos
- En general, las opciones de temporización son muy configurables y ofrecen un control fino
Compensar de inmediato ejecuciones perdidas
Persistent= es especialmente adecuado para scripts programados que no deberían omitirse por culpa de una laptop suspendida, pero que tampoco necesitan llegar al extremo de WakeSystem=
Persistent=
Takes a boolean argument. If true, the time when the service
unit was last triggered is stored on disk. When the timer is
activated, the service unit is triggered immediately if it
would have been triggered at least once during the time when
the timer was inactive. ...
- Si un sistema con check-ins programados de gestión de configuración sufrió tiempo fuera de servicio, basta con poner
Persistent= en el .timer para converger al estado correcto apenas vuelva en línea
- Sin
Persistent=, puede tocar esperar hasta la siguiente hora normal de ejecución del temporizador, y ese tiempo podría ser largo
- Otros trabajos donde no conviene esperar al detectar una activación perdida incluyen actualizaciones del sistema y verificación de tareas por lotes
Puntos a cuidar al escribir temporizadores
- Los temporizadores en el contexto del administrador de usuario que se manejan con
systemctl --user también son válidos, pero hay que fijarse bien en el destino usado en [Install]
- Según la distribución, el destino apropiado para temporizadores de usuario puede ser
default.target
- Igual que con
cron, sigue aplicando la advertencia general de mantener un reloj del sistema preciso
- Los usuarios de systemd pueden revisar el estado de sincronización con
timedatectl timesync-status
- Muchos editores ya soportan de forma nativa el formato de archivos de unidad de systemd, lo que ayuda cuando los archivos crecen
- En Emacs se puede usar el paquete emacs systemd
1 comentarios
Comentarios de Lobste.rs
systemd no es perfecto, pero da la impresión de que muchas decisiones de diseño se basan en lecciones aprendidas de enfoques más tradicionales del pasado.
Hace poco volví a escuchar este episodio de CRE de 2015 en el que Lennart Poettering explica ese contexto, y todavía vale la pena recomendarlo.
Soy de los que no soportan systemd hasta la médula, pero creo que systemd.timers es una de las ideas “menos malas” de este producto.
Por eso me sorprendió un poco que el autor lo defendiera desacreditando a gente con quejas válidas.
Aun así, me gusta usarlo junto con el comando
at. Para comandos que deben ejecutarse una sola vez en un momento específico, usoat; para lo demás, temporizadores de systemd y archivos de unidad simples.La mejora que más me gustaría ver es poder saber qué usuario está ejecutando un temporizador. Soy uno de los pocos que todavía administran una shell box en 2026, pero sería útil poder identificar qué usuario creó el temporizador que está golpeando el disco cada segundo.
Según entiendo, con
loginctl enable-lingerpueden ejecutarse incluso sin una sesión de usuario activa. Claro, habrá casos de uso donde eso no sea suficiente, y no conozco la situación concreta.Ojalá los temporizadores de systemd tuvieran una barrera de entrada más baja, sobre todo del lado de la administración de usuarios.
Viendo la cantidad de configuración necesaria, de verdad es difícil ganarle a
crontab -e.Pasé mucho tiempo pensando cómo recopilar de forma ordenada los logs de scripts de cron, hasta que me di cuenta de que simplemente podía usar temporizadores de systemd.
El problema del logging quedó resuelto. Ya no tengo motivo para volver a usar cron, y ojalá lo hubiera sabido antes.
logger, agregarlo a un archivo de log con>>, o dejarlo con la configuración por defecto y recibir correos?Llámenme anticuado, pero todavía tengo configurado el correo electrónico para que el servidor me contacte.
Si lo automatizas, viene gratis en cada host nuevo, y en el día a día sigue siendo bastante conveniente.
Por ejemplo, abro un multiplexor, ejecuto
long_running_process | mail root@localhost -s "done $?"y luego me olvido del asunto.Buen artículo; yo también tenía un borrador de algo parecido y hace poco tuve que volver a consultarlo.
Si, como yo, te metes en la madriguera de conejo de systemd, recomiendo dejar los archivos de unidad y los temporizadores del proyecto correspondiente como enlaces simbólicos en
/etc/systemd/system/.Una queja que tengo sobre systemd es que no distingue entre las unidades instaladas por la distribución y las que escribiste tú mismo, pero con enlaces simbólicos puedes mantener esa separación por tu cuenta.
Las unidades del sistema/paquete/distribución van en
/usr/lib/systemd/system, y los overrides locales o unidades locales van en/etc/systemd/system.