unattended-upgrades actualizaba todo menos lo que yo creía
Mi VM se parchea sola y reinicia a las 04:00, pero un paquete de Microsoft se quedaba atrás. Qué instala de verdad unattended-upgrades, qué no, y cómo lo tengo configurado.

Tengo unattended-upgrades activado desde el principio, con reinicio automático a las 04:00, y vivía tranquilo pensando que la máquina se mantenía al día sola. Hasta que me puse a mirar qué tenía pendiente:
$ apt list --upgradable
aadsshlogin/noble 1.0.036000002 amd64 [upgradable from: 1.0.035540001]
apache2-utils/noble-security 2.4.58-1ubuntu8.19 amd64 [upgradable from: 2.4.58-1ubuntu8.18]
libc6/noble-updates,noble-security 2.39-0ubuntu8.12 amd64 [upgradable from: 2.39-0ubuntu8.9]
...
Lynis, que pasa cada noche, también lo había visto: un warning PKGS-7392 (“Found one or more vulnerable packages”) y el hardening index en 69. Hay dos cosas distintas mezcladas en esa lista, y separarlas fue lo que me enseñó cómo funciona esto de verdad.
Lo que unattended-upgrades instala de verdad
unattended-upgrades decide según el repositorio del que viene cada paquete, da igual lo importante que sea el paquete. Según el README del proyecto, se fija en el Origin y el Suite del fichero Release de cada repo. En Ubuntu, la lista Allowed-Origins de /etc/apt/apt.conf.d/50unattended-upgrades trae de serie el archivo de la release, el pocket de seguridad y los de ESM. La documentación de Ubuntu Server la copia tal cual, y ahí se ve que hasta -updates viene comentado.
Yo no toco ese fichero. El README recomienda poner los cambios en uno propio con un número mayor, para que las actualizaciones del paquete no choquen con lo tuyo, y el mío es 52unattended-upgrades-local.conf:
Unattended-Upgrade::Automatic-Reboot "true";
Unattended-Upgrade::Automatic-Reboot-WithUsers "false";
Unattended-Upgrade::Automatic-Reboot-Time "04:00";
Unattended-Upgrade::Allowed-Origins:: "${distro_id}:${distro_codename}-updates";
Unattended-Upgrade::Origins-Pattern:: "origin=packagecloud.io/crowdsec/crowdsec,codename=noble";
Al arrancar, el log de unattended-upgrades dice con qué lista se queda, que es la forma más rápida de comprobar que el fichero se lee:
INFO Allowed origins are: o=Ubuntu,a=noble, o=Ubuntu,a=noble-security,
o=UbuntuESMApps,a=noble-apps-security, o=UbuntuESM,a=noble-infra-security,
o=Ubuntu,a=noble-updates, origin=packagecloud.io/crowdsec/crowdsec,codename=noble
Así que los libc6 y compañía no eran un fallo. La última pasada había sido a las 06:07 y solo encontró libmysqlclient21. Los parches de libc se publicaron después, y entran en la siguiente. Que algo aparezca en apt list --upgradable unas horas no quiere decir que se esté quedando atrás.
El de Microsoft es otra historia. Ese repo no está en ninguna de las listas, y ahí se iba a quedar para siempre.
Cómo se ve el origen de un repo
$ apt-cache policy aadsshlogin
aadsshlogin:
Installed: 1.0.035540001
Candidate: 1.0.036000002
...
$ apt-cache policy | grep -A1 microsoft
500 https://packages.microsoft.com/repos/microsoft-ubuntu-noble-prod noble/main amd64 Packages
release o=microsoft-ubuntu-noble-prod noble,a=noble,n=noble,l=microsoft-ubuntu-noble-prod noble,c=main,b=amd64
origin packages.microsoft.com
En la línea release, o= es el origin y a= el archive (la suite). Con esos dos valores se escriben las entradas de Allowed-Origins, en formato "origin:archive". La línea origin que va debajo es el nombre del servidor, lo que en Origins-Pattern se llama site. Que el origin de Microsoft lleve espacios dentro (microsoft-ubuntu-noble-prod noble) ya da una idea de lo cómodo que es escribirlo a mano.
Y para comprobar qué haría sin que llegue a instalar nada:
sudo unattended-upgrade --dry-run --debug
El detalle de cada ejecución acaba en /var/log/unattended-upgrades/. Si hubiera mirado ese log alguna vez en lugar de fiarme, me habría ahorrado la sorpresa.
El reinicio de las 04:00
Solo reinicia si, al terminar de actualizar, existe /var/run/reboot-required, que es lo que dejan los paquetes del kernel y similares. Automatic-Reboot-WithUsers vale true por defecto, es decir, reinicia aunque haya alguien conectado. Yo lo tengo a false: si a las cuatro de la mañana estoy dentro por SSH, probablemente sea porque estoy arreglando algo y no quiero que la máquina se me vaya debajo. El precio es que, si me dejo una sesión abierta, el reinicio no llega a hacerse.
Luego está cuándo se ejecuta la actualización en sí. Yo daba por hecho que había un cron a las 04:00, y resulta que la lanzan apt-daily.timer y apt-daily-upgrade.timer, con un retraso aleatorio:
$ systemctl list-timers 'apt-daily*'
NEXT LEFT LAST UNIT
Fri 2026-11-13 06:37:49 UTC 6h Thu 2026-11-12 06:07:37 UTC apt-daily-upgrade.timer
Fri 2026-11-13 10:48:09 UTC 10h Thu 2026-11-12 22:47:27 UTC apt-daily.timer
En la práctica, unattended-upgrades instala cuando le toca y, si hace falta reiniciar, programa el reinicio para la siguiente vez que el reloj marque las 04:00. Y ojo, que esas 04:00 son las del sistema. La VM está en UTC, así que en Barcelona el reinicio cae a las 5 en invierno y a las 6 en verano. Para un portfolio, un minuto caído de madrugada no le importa a nadie, y la status page lo deja registrado. Prefiero eso a tener un kernel parcheado en disco y el viejo en memoria durante semanas.
Dos paquetes de fuera
aadsshlogin
Viene del repositorio de Microsoft y llega con la extensión AADSSHLoginForLinux de Azure, que sirve para entrar por SSH con cuentas de Entra ID. Yo entro solo con mi llave ed25519, así que es una vía de autenticación que no uso para nada y que encima no se actualizaba sola.
Veo dos opciones. Una es meter el repo en Origins-Pattern, que acepta patrones del estilo "site=packages.microsoft.com". No me convence porque ese repo es enorme y cualquier cosa que instalara desde ahí se actualizaría sin que me enterase. La otra es quitar la extensión desde el portal de Azure y olvidarme, que me parece más limpia. Mientras me decido, a mano:
sudo apt update && sudo apt install --only-upgrade aadsshlogin
CrowdSec
Con CrowdSec es al revés: lo instalé desde fuera a propósito. Ubuntu 24.04 trae una versión bastante antigua y yo uso la 1.8.2 del repo de packagecloud. Para que ese repo no pueda colarme nada más, tiene un pin en /etc/apt/preferences.d/crowdsec-upstream.pref:
# Upstream CrowdSec repo (packagecloud): only crowdsec packages may come from it.
Package: *
Pin: origin packagecloud.io
Pin-Priority: -1
Package: crowdsec crowdsec-firewall-bouncer-nftables
Pin: origin packagecloud.io
Pin-Priority: 990
Prioridad -1 para todo lo que venga de ahí (no se instala nunca) y 990 solo para los dos paquetes que quiero. Leyendo la man page de apt_preferences vi un detalle que me tranquilizó: en un fichero de preferencias, Pin: origin se refiere al nombre del servidor y no al campo Origin del Release. Para lo segundo hay que usar Pin: release o=.... Es justo lo que quería, porque packagecloud.io es el servidor, pero la man page lo avisa expresamente porque es fácil confundirlos. Con apt-cache policy se ve que funciona: el repo sale con un -1 delante.
Este repo sí lo dejé en Origins-Pattern, y no sin dudas. El bouncer de nftables crea la cadena crowdsec-chain-input, y por encima de su drop tengo una regla accept para los puertos 22, 80 y 443 desde <RANGOS_UAB>, que reaplico con un ExecStartPost en la unidad del bouncer. Si una versión nueva cambia cómo monta la cadena, mi regla podría fallar en silencio, y me enteraría el día que volviera a no poder entrar desde la universidad. Ya me pasó una vez. Al final pudo más no quedarme con un CrowdSec viejo, pero después de cada actualización lo compruebo:
sudo nft list ruleset | grep -B2 -A2 uab-allow
El README dice que, si ya controlas con pinning qué se instala, puedes poner "origin=*" y dejar que unattended-upgrades lo actualice todo. En una máquina donde cada repo de fuera está por un motivo distinto, yo no lo haría.
Reiniciar servicios sin reiniciar la máquina
Lynis traía otra sugerencia, LOGG-2190: ficheros borrados que algún proceso todavía usa. Cuando se sustituye una librería, lo que ya está corriendo sigue con la versión vieja cargada en memoria.
Para eso está needrestart, que en 24.04 cambió bastante. Según el anuncio en el Discourse de Ubuntu, se ejecuta al final de cada transacción de APT, también con unattended-upgrades, y ahora reinicia directamente los servicios afectados en vez de limitarse a listarlos. Tampoco lo reinicia todo. Hay una lista de excepciones, y lo que no es un servicio (una sesión SSH abierta, por ejemplo) se queda como está, así que Lynis puede seguir encontrando cosas. Para ver qué queda:
$ sudo needrestart -r l -b
NEEDRESTART-VER: 3.6
NEEDRESTART-KCUR: 7.0.0-1017-azure
NEEDRESTART-KEXP: 7.0.0-1017-azure
NEEDRESTART-KSTA: 1
NEEDRESTART-SVC: dbus.service
NEEDRESTART-SVC: networkd-dispatcher.service
NEEDRESTART-SVC: systemd-logind.service
NEEDRESTART-SVC: unattended-upgrades.service
NEEDRESTART-SESS: azureuser @ user manager service
Con l solo lista, y -b lo da en formato fácil de procesar. El kernel está bien (KSTA: 1, el que corre es el esperado), pero hay cuatro servicios con librerías viejas cargadas. Son justo de los que needrestart no reinicia solo por defecto, porque tocar dbus o systemd-logind en caliente puede tirarte la sesión. Esos esperan al siguiente reinicio de las 04:00. Si quieres cambiar qué se reinicia solo, la documentación de Ubuntu explica cómo hacerlo con $nrconf{override_rc} en un fichero de /etc/needrestart/conf.d/, sin tocar el principal.
Lo siguiente es que el informe diario del servidor liste aparte los paquetes de repos de terceros con actualización disponible, para no depender de que Lynis se queje. Y decidir de una vez qué hago con la extensión de Azure.