Ubuntu 24.04: “Failed to start …” — el flujo de trabajo de triage systemd más rápido (caso #62)

¿Te fue útil?

Son las 02:13. El pager indica “servicio caído”, el panel está en línea plana y tu consola te saluda con: Failed to start …. Ese mensaje es la versión de systemd de “algo pasó”. Tan útil como una galleta de la fortuna.

Este es el flujo de trabajo que uso en Ubuntu 24.04 cuando necesito una respuesta rápida: qué falló, por qué falló, de qué depende y si debemos arreglar, revertir o aislar. Sin misticismo. Solo el camino más corto del síntoma a la decisión.

El modelo mental: qué significa realmente “Failed to start”

Systemd es un orquestador, no un psíquico. Cuando imprime “Failed to start”, está reportando una transición de estado: una unidad pasó de “activating” a “failed”, o nunca llegó a “active”, o alcanzó un límite de reinicios, o una de sus dependencias falló primero y systemd solo es el mensajero.

En Ubuntu 24.04, verás que se repite el mismo puñado de razones subyacentes:

  • Definición de unidad incorrecta: typo, ruta errónea, comillas faltantes, directiva inválida, Type= incorrecto.
  • Fallo en tiempo de ejecución: ejecutable sale con código distinto de cero, se bloquea, supera el tiempo de espera o no puede enlazar un puerto.
  • Fallo de dependencia: montaje ausente, red no disponible, secretos no legibles, base de datos caída.
  • Desajuste de entorno: archivo de configuración movido, usuario cambiado, permisos modificados, conflicto con perfil SELinux/AppArmor.
  • Presión a nivel de sistema: disco lleno, presión de memoria (OOM), límites de descriptores de archivos, cuotas de CPU, restricciones de cgroup.
  • Limitación de arranque: “Start request repeated too quickly”, es decir, hiciste un bucle de reinicios y systemd se cansó.

La clave es tratar systemd como un motor de grafos. Las unidades tienen orden (After=, Before=) y requisitos (Requires=, Wants=). El orden es el “cuándo”, los requisitos son el “debe existir”. Confundirlos es un generador clásico de outages.

Hechos interesantes y contexto histórico (porque el pasado sigue apareciendo en tus incidentes)

  1. Systemd llegó a Linux mainstream alrededor de 2010–2012 y reemplazó una colección de scripts init por un gestor único consciente de dependencias.
  2. Ubuntu cambió de Upstart a systemd en 15.04; una década después, muchas “suposiciones de scripts init” aún acechan en servicios personalizados.
  3. journald es binario por defecto (metadatos estructurados, filtrado rápido), lo cual es genial hasta que olvidas persistir logs y un reinicio borra la escena del crimen.
  4. Las unidades no son solo servicios: montajes, sockets, timers, paths, scopes, targets—la mayoría de eventos de “servicio fallido” empiezan como un montaje o socket fallido.
  5. La limitación de arranque existe para proteger el host; sin ella, un servicio en crash-loop puede DOSear su propia máquina con tormentas fork/exec.
  6. “network-online” de systemd es deliberadamente impreciso: significa “un gestor de red dice que está online”, no “tu endpoint SaaS es accesible”.
  7. Los valores por defecto de timeouts han cambiado con el tiempo; archivos unit viejos a veces tienen timeouts demasiado cortos para rutas de arranque modernas o demasiado largos para expectativas de producción.
  8. cgroups v2 es el mundo por defecto ahora; los controles de recursos y el comportamiento OOM pueden diferir de hosts antiguos, sorprendiendo a servicios que “siempre funcionaron”.

Y una cita operacional que vale la pena tener pegada al terminal:

“La esperanza no es una estrategia.” — General Gordon R. Sullivan

El triage de systemd es el antídoto contra la esperanza.

Guía rápida de diagnóstico (primeras/segundas/terceras comprobaciones)

Si solo recuerdas una cosa: no empieces editando archivos. Comienza por recopilar evidencia y luego elige la intervención más pequeña que restaure el servicio de forma segura.

Primero: identifica exactamente qué falló y cómo

  • Revisa el estado de la unidad y el último resultado (systemctl status).
  • Extrae los logs relevantes del arranque actual (journalctl -u ... -b).
  • Confirma si es una falla directa o una cascada de dependencias (systemctl list-dependencies y systemctl show).

Segundo: clasifica la falla en 60 segundos

  • Exec/exit: busca código de salida, señal, core dump, archivo faltante.
  • Timeout: “start operation timed out”, a menudo esperando montajes, network-online o disco lento.
  • Permisos: “permission denied”, “cannot open” o denegaciones de AppArmor.
  • Recursos: OOM kill, ENOSPC, demasiados archivos abiertos.
  • Throttle de reinicios: “start-limit-hit” o “request repeated too quickly”.

Tercero: elige la acción correctiva de menor riesgo

  • Rollback conocido y bueno: revierte el último cambio de config/paquete si la línea de tiempo coincide.
  • Aislamiento temporal: detén unidades dependientes, enmascara las que están en flapping o desactiva el timer para estabilizar el host.
  • Arreglo quirúrgico: ajusta el archivo de unidad, permisos, orden de montajes o el archivo de entorno—and reinicia con estado limpio.

Un chiste corto, porque lo necesitarás: los logs de systemd son como novelas policiacas—todos son sospechosos y el perpetrador suele ser “un archivo faltante”.

Doce+ tareas prácticas con comandos, significado de la salida y decisiones

Estos son los movimientos que hago en Ubuntu 24.04 cuando una unidad falla. Cada tarea incluye el comando, un fragmento realista de salida, lo que significa y qué decisión tomar a continuación.

Task 1: Confirmar el estado actual de la unidad, el último código de salida y la pista inmediata

cr0x@server:~$ systemctl status nginx.service
× nginx.service - A high performance web server and a reverse proxy server
     Loaded: loaded (/lib/systemd/system/nginx.service; enabled; preset: enabled)
     Active: failed (Result: exit-code) since Mon 2025-12-30 02:11:02 UTC; 1min 4s ago
   Duration: 83ms
       Docs: man:nginx(8)
    Process: 2191 ExecStartPre=/usr/sbin/nginx -t -q -g daemon on; master_process on; (code=exited, status=1/FAILURE)
        CPU: 72ms

Dec 30 02:11:02 server nginx[2191]: nginx: [emerg] open() "/etc/nginx/snippets/tls.conf" failed (2: No such file or directory)
Dec 30 02:11:02 server systemd[1]: nginx.service: Control process exited, code=exited, status=1/FAILURE
Dec 30 02:11:02 server systemd[1]: nginx.service: Failed with result 'exit-code'.
Dec 30 02:11:02 server systemd[1]: Failed to start nginx.service - A high performance web server and a reverse proxy server.

Significado: El fallo está en la prueba de configuración de ExecStartPre; nginx se negó a arrancar por un archivo faltante.

Decisión: No reinicies a ciegas. Arregla el include faltante o revierte el cambio de configuración. Verifica con nginx -t una vez corregido.

Task 2: Extraer el slice completo del journal para la unidad, desde el arranque actual

cr0x@server:~$ journalctl -u nginx.service -b --no-pager -n 80
Dec 30 02:11:02 server nginx[2191]: nginx: [emerg] open() "/etc/nginx/snippets/tls.conf" failed (2: No such file or directory)
Dec 30 02:11:02 server systemd[1]: nginx.service: Control process exited, code=exited, status=1/FAILURE
Dec 30 02:11:02 server systemd[1]: nginx.service: Failed with result 'exit-code'.
Dec 30 02:11:02 server systemd[1]: Failed to start nginx.service - A high performance web server and a reverse proxy server.

Significado: Journald confirma la misma causa raíz; no hace falta perseguir dependencias todavía.

Decisión: Arregla la ruta del archivo de configuración o restaura el snippet; luego prueba y arranca de nuevo.

Task 3: Si es una cascada de dependencias, pregunta a systemd qué cree que desencadenó la parada

cr0x@server:~$ systemctl show -p Id -p ActiveState -p SubState -p Result -p ExecMainStatus -p ExecMainCode -p NRestarts nginx.service
Id=nginx.service
ActiveState=failed
SubState=failed
Result=exit-code
ExecMainCode=1
ExecMainStatus=1
NRestarts=0

Significado: Esto es un fallo limpio “process exited 1”, no una señal kill, ni un bucle de reinicios.

Decisión: Arregla el ejecutable/config subyacente; no toques límites de inicio ni la política de reinicio.

Task 4: Mostrar dependencias de la unidad y buscar requisitos “muertos”

cr0x@server:~$ systemctl list-dependencies --reverse nginx.service
nginx.service
● nginx.service
○ systemd-user-sessions.service
○ multi-user.target

Significado: Nada especial depende de nginx aparte del target; las dependencias inversas no bloquearán un reinicio.

Decisión: Seguro reiniciar después de arreglar la config; bajo radio de impacto.

Task 5: Verificar la corrección del archivo de unidad y los override (cazar “no está usando lo que crees”)

cr0x@server:~$ systemctl cat nginx.service
# /lib/systemd/system/nginx.service
[Unit]
Description=A high performance web server and a reverse proxy server
After=network-online.target remote-fs.target nss-lookup.target
Wants=network-online.target

[Service]
Type=forking
PIDFile=/run/nginx.pid
ExecStartPre=/usr/sbin/nginx -t -q -g 'daemon on; master_process on;'
ExecStart=/usr/sbin/nginx -g 'daemon on; master_process on;'
ExecReload=/usr/sbin/nginx -g 'daemon on; master_process on;' -s reload
ExecStop=/sbin/start-stop-daemon --quiet --stop --retry QUIT/5 --pidfile /run/nginx.pid
TimeoutStopSec=5
KillMode=mixed

[Install]
WantedBy=multi-user.target

Significado: No se muestran drop-ins; esta unidad usa la definición empaquetada. Si esperabas overrides personalizados, estás depurando el archivo equivocado.

Decisión: Si se requiere personalización, crea un drop-in con systemctl edit en lugar de modificar archivos bajo /lib.

Task 6: Validar un override drop-in y comprobar configuraciones sutiles

cr0x@server:~$ systemctl status myapp.service
× myapp.service - My App API
     Loaded: loaded (/etc/systemd/system/myapp.service; enabled; preset: enabled)
    Drop-In: /etc/systemd/system/myapp.service.d
             └─override.conf
     Active: failed (Result: exit-code) since Mon 2025-12-30 02:08:19 UTC; 2min 53s ago
Dec 30 02:08:19 server systemd[1]: myapp.service: Failed to run 'start' task: No such file or directory
cr0x@server:~$ systemctl cat myapp.service
# /etc/systemd/system/myapp.service
[Service]
ExecStart=/opt/myapp/bin/myapp --config /etc/myapp/config.yaml
User=myapp
Group=myapp
EnvironmentFile=/etc/myapp/myapp.env

# /etc/systemd/system/myapp.service.d/override.conf
[Service]
ExecStart=/opt/myapp/bin/myappd --config /etc/myapp/config.yaml

Significado: Los drop-in sobrescriben ExecStart. Si /opt/myapp/bin/myappd no existe, systemd no puede ejecutarlo.

Decisión: Arregla la ruta en el override o elimina el drop-in; luego systemctl daemon-reload y reinicia.

Task 7: Detectar “start-limit-hit” (throttle de reinicios) y resetearlo correctamente

cr0x@server:~$ systemctl status myapp.service
× myapp.service - My App API
     Loaded: loaded (/etc/systemd/system/myapp.service; enabled; preset: enabled)
     Active: failed (Result: start-limit-hit) since Mon 2025-12-30 02:09:02 UTC; 2min 10s ago
Dec 30 02:09:02 server systemd[1]: myapp.service: Scheduled restart job, restart counter is at 5.
Dec 30 02:09:02 server systemd[1]: myapp.service: Start request repeated too quickly.
Dec 30 02:09:02 server systemd[1]: myapp.service: Failed with result 'start-limit-hit'.

Significado: El servicio estuvo en flapping y systemd lo limitó. Esto suele ser un síntoma, no la enfermedad.

Decisión: Arregla el crash/exit primero. Una vez solucionado, limpia el throttle:

cr0x@server:~$ sudo systemctl reset-failed myapp.service

Significado: Borra el estado de fallo para que se permitan nuevos intentos de inicio.

Decisión: Inicia el servicio solo después de eliminar la causa subyacente, o volverás a disparar el throttle.

Task 8: Identificar un timeout vs un crash real

cr0x@server:~$ systemctl status postgresql.service
× postgresql.service - PostgreSQL RDBMS
     Loaded: loaded (/lib/systemd/system/postgresql.service; enabled; preset: enabled)
     Active: failed (Result: timeout) since Mon 2025-12-30 02:05:41 UTC; 6min ago
Dec 30 02:04:11 server systemd[1]: Starting postgresql.service - PostgreSQL RDBMS...
Dec 30 02:05:41 server systemd[1]: postgresql.service: start operation timed out. Terminating.
Dec 30 02:05:41 server systemd[1]: postgresql.service: Failed with result 'timeout'.

Significado: No terminó rápido; se quedó colgado durante el arranque. Culpables comunes: almacenamiento lento, recuperación WAL, directorio de datos bloqueado, retraso DNS o una dependencia de montaje.

Decisión: Revisa logs, salud del almacenamiento y si el directorio de datos está en un montaje que no estaba listo.

Task 9: Correlacionar el tiempo de inicio de la unidad con la disponibilidad del almacenamiento/montaje

cr0x@server:~$ systemd-analyze critical-chain postgresql.service
postgresql.service +1min 28.122s
└─local-fs.target @12.405s
  └─mnt-data.mount @11.902s +1min 15.701s
    └─systemd-fsck@dev-disk-by\x2duuid-3a1c...service @3.211s +8.614s
      └─dev-disk-by\x2duuid-3a1c....device @2.983s

Significado: La demora real es que mnt-data.mount tardó 75 segundos. Postgres solo está esperando al sistema de archivos.

Decisión: Arregla el rendimiento del montaje (¿almacenamiento en red? ¿fsck? ¿errores de dispositivo?). O desacopla el orden de inicio si es seguro, pero no enmascares un disco que falla.

Task 10: Inspeccionar fallos de unidades de montaje y errores en fstab

cr0x@server:~$ systemctl status mnt-data.mount
× mnt-data.mount - /mnt/data
     Loaded: loaded (/etc/fstab; generated)
     Active: failed (Result: exit-code) since Mon 2025-12-30 02:03:22 UTC; 8min ago
Dec 30 02:03:22 server mount[612]: mount: /mnt/data: wrong fs type, bad option, bad superblock on /dev/sdb1, missing codepage or helper program.
Dec 30 02:03:22 server systemd[1]: mnt-data.mount: Mount process exited, code=exited, status=32/n/a
Dec 30 02:03:22 server systemd[1]: mnt-data.mount: Failed with result 'exit-code'.

Significado: El montaje está roto; cualquier servicio que dependa de él fallará o quedará colgado. “wrong fs type” puede significar tipo equivocado en fstab, paquete de sistema de archivos faltante o corrupción real.

Decisión: Confirma dispositivo y tipo de sistema de archivos. Si esto es datos de producción, evita seguir intentando y valida el dispositivo de bloque antes de que intentos repetidos lo empeoren.

Task 11: Confirmar qué sistema de archivos detecta el kernel (y si el dispositivo existe)

cr0x@server:~$ lsblk -f
NAME   FSTYPE FSVER LABEL UUID                                 FSAVAIL FSUSE% MOUNTPOINTS
sda
├─sda1 vfat   FAT32       7C1A-3F2B                             510M     2% /boot/efi
├─sda2 ext4   1.0         2d4b2b3c-5a62-4b2f-8f87-1b9e8f8a0c19  14G    61% /
└─sda3 swap   1           7d3e0c7c-5d48-4d1b-9b8b-2a5d0f3b9e21                [SWAP]
sdb
└─sdb1 xfs                9a1e3f1f-6a71-4bb5-8a45-2e7a5bb1c5b2

Significado: /dev/sdb1 es XFS. Si fstab dice ext4, ese es tu fallo. Si fstab dice xfs y aún falla, sospecha de reparaciones XFS pendientes o falta de xfsprogs (raro en Ubuntu, pero posible en builds mínimos).

Decisión: Arregla el tipo/opciones en fstab, o ejecuta comprobaciones de sistema de archivos en modo mantenimiento. No fuerces opciones de montaje en un disco enfermo en producción a menos que te guste la pérdida de datos sorpresa.

Task 12: Comprobar si un servicio fue matado por OOM (el asesino silencioso)

cr0x@server:~$ journalctl -b -k --no-pager | tail -n 12
Dec 30 02:07:12 server kernel: Out of memory: Killed process 3310 (myapp) total-vm:3128456kB, anon-rss:1452032kB, file-rss:132kB, shmem-rss:0kB
Dec 30 02:07:12 server kernel: oom_reaper: reaped process 3310 (myapp), now anon-rss:0kB, file-rss:0kB, shmem-rss:0kB
Dec 30 02:07:12 server systemd[1]: myapp.service: Main process exited, code=killed, status=9/KILL
Dec 30 02:07:12 server systemd[1]: myapp.service: Failed with result 'signal'.

Significado: El kernel lo mató, no systemd. Systemd informa las consecuencias: SIGKILL.

Decisión: Esto es trabajo de capacidad/límites: ajusta memoria, reduce uso, añade swap (con precaución), corrige fugas o fija límites de cgroup sensatos. Reiniciar solo es una mentira temporal.

Task 13: Comprobar “address already in use” (conflictos de puerto) y elegir al culpable correcto

cr0x@server:~$ systemctl status myapp.service
× myapp.service - My App API
     Loaded: loaded (/etc/systemd/system/myapp.service; enabled; preset: enabled)
     Active: failed (Result: exit-code) since Mon 2025-12-30 02:12:48 UTC; 10s ago
Dec 30 02:12:48 server myapp[4021]: listen tcp 0.0.0.0:8080: bind: address already in use
cr0x@server:~$ sudo ss -ltnp | grep ':8080'
LISTEN 0      4096         0.0.0.0:8080      0.0.0.0:*    users:(("nginx",pid=2101,fd=12))

Significado: Nginx (u otro) posee el puerto. Tu app no puede bindearlo.

Decisión: Decide quién debe ocupar el puerto. O mueve la app a otro puerto, actualiza el reverse proxy o detén el servicio en conflicto. No mates PIDs al azar sin saber por qué están ahí.

Task 14: Confirmar si AppArmor bloqueó el acceso (común en Ubuntu)

cr0x@server:~$ journalctl -b --no-pager | grep -i apparmor | tail -n 6
Dec 30 02:10:02 server kernel: audit: type=1400 audit(1735524602.112:91): apparmor="DENIED" operation="open" profile="/usr/sbin/nginx" name="/etc/ssl/private/my.key" pid=2191 comm="nginx" requested_mask="r" denied_mask="r" fsuid=0 ouid=0

Significado: El servicio está bloqueado para leer un archivo que necesita. Esto parece un problema de permisos pero no se arregla con chmod 777 (no lo hagas).

Decisión: Ajusta el perfil de AppArmor correctamente o reubica secretos a rutas aprobadas; luego recarga el perfil y reinicia.

Task 15: Verificar que la unidad pueda ver el entorno que crees que tiene

cr0x@server:~$ systemctl show myapp.service -p Environment -p EnvironmentFiles
Environment=
EnvironmentFiles=/etc/myapp/myapp.env (ignore_errors=no)
cr0x@server:~$ sudo test -r /etc/myapp/myapp.env && echo readable || echo not_readable
not_readable

Significado: El archivo de entorno no es legible por root (o por systemd durante la carga), por lo que la unidad puede fallar al arrancar o faltar variables.

Decisión: Arregla la propiedad/permisos. Para secretos: legible por root, legible por el usuario del servicio solo si se necesita, y evita configs legibles por todos.

Modos de fallo que importan en Ubuntu 24.04

1) Confusión de dependencias: After= no es Requires=

After=network-online.target significa “iniciar después de ese target”, no “fallar si network-online falla”. Si tu servicio realmente requiere algo, decláralo. A la inversa, si declaras Requires= sobre algo inestable, tu servicio caerá cada vez que esa dependencia tenga un tropiezo.

2) Unidades oneshot que fingen ser servicios de larga duración

Muchos servicios internos son en realidad “ejecutar un script para hacer setup” y luego salir. Si la unidad está definida como Type=simple con un proceso de corta duración, systemd pensará que se cayó. Usa Type=oneshot con RemainAfterExit=yes cuando corresponda.

3) network-online es una trampa (y no siempre tu amiga)

En instancias cloud, “network online” puede volverse verdadero antes de que DNS funcione, antes de que las rutas converjan o antes de que tu red overlay esté funcional. Si tu servicio necesita alcanzar una base de datos remota durante el inicio, prefiere lógica de reintento explícita en la app. El orden de systemd no te salvará de upstreams inestables.

4) Retrasos de almacenamiento: tu servicio es inocente, tu montaje es culpable

Cuando el almacenamiento es lento, los servicios hacen timeout. Cuando el almacenamiento está roto, los servicios fallan. En cualquier caso, la unidad señalada rara vez es la que causó la demora.

En Ubuntu 24.04, presta atención a:

  • entradas fstab que generan unidades de montaje
  • comportamiento de remote-fs (NFS, CIFS)
  • retrasos fsck
  • renumeración de dispositivos tras cambios de hardware

5) El mito de “funcionó ayer”: deriva de paquetes y configuraciones

Ubuntu 24.04 trae systemd más nuevo, OpenSSL con nuevos defaults, Python más reciente y kernels nuevos. Servicios que tenían comportamiento indefinido en máquinas antiguas pueden volverse correctamente rotos. No luches contra eso. Corrige las suposiciones.

Tres mini-historias del mundo corporativo (anonimizadas, plausibles y dolorosas)

Mini-historia 1: El outage causado por una suposición equivocada

Un equipo migró una flota a Ubuntu 24.04 y “estandarizó” archivos de unidades. Alguien asumió que After=network-online.target significaba que el servicio no arrancaría hasta que la base de datos fuera accesible. En staging parecía correcto. En producción, un subconjunto de hosts arrancó con DNS retrasado tras un cambio de red.

El servicio intentó conectarse a la base de datos al inicio, falló una vez y salió. La política de reinicio estaba en Restart=on-failure con un bucle apretado. Systemd hizo lo que debe: reinició repetidamente y luego limitó con start-limit-hit. Ahora el servicio estaba abajo y se negaba a arrancar, incluso después de que DNS se estabilizara.

La respuesta inicial fue predecible: la gente ajustó StartLimitIntervalSec y StartLimitBurst para “dejarlo seguir intentando”. Eso creó un nuevo problema: el crash loop martilló DNS y la base de datos con tormentas de conexión desde cada host que se reiniciaba. Así se convierte una falla local en un incidente compartido.

La solución fue aburrida y correcta: la app incorporó backoff exponencial y reintentos, el inicio toleró fallos iniciales de upstream y el orden de systemd se simplificó. Mantuvieron After=network-online.target por limpieza, pero dejaron de pretender que era una garantía de conectividad.

Mini-historia 2: La optimización que salió mal

Una iniciativa de ahorro impulsó arranques más rápidos y menor uso de memoria. Alguien configuró timeouts agresivos globalmente y apretó límites de memoria para un lote de unidades de procesamiento de datos. El arranque mejoró en pruebas sintéticas y apareció una diapositiva.

En la práctica, un subconjunto de nodos tenía almacenamiento adjunto más lento (no roto, solo más lento en ciertos momentos). Postgres en esos nodos ocasionalmente necesitaba más tiempo para recuperación tras apagados no limpios. Los nuevos timeouts lo mataban a mitad de recuperación. No solo el servicio fallaba al iniciar, sino que las recuperaciones interrumpidas repetidas hicieron que el arranque fuera aún más lento y el bucle de reinicios multiplicó el dolor.

Operaciones culpó a la base de datos. El equipo de base de datos culpó al kernel. El kernel culpó al hipervisor. Mientras tanto, la causa raíz fue una “mejora de rendimiento” que eliminó la holgura que el sistema necesitaba.

El rollback fue inmediato: restaurar timeouts sensatos y luego ajustar valores por servicio basados en tiempos de recuperación observados. La solución a largo plazo fue una política: los timeouts son contratos por servicio, no deseos globales. Y si quieres arranques más rápidos, arregla los cuellos de botella reales—usualmente almacenamiento y red, no el cronómetro.

Mini-historia 3: La práctica aburrida que salvó el día

Un servicio relacionado con pagos corría en un clúster con control estricto de cambios. No era glamuroso. Cada unidad systemd tenía un drop-in en control de versiones y cada despliegue incluía un “smoke reboot” en un canario para capturar problemas de orden de arranque y dependencias.

Un día, un parche rutinario introdujo una entrada en fstab para un nuevo montaje de reporting. El servidor de montaje estaba disponible, pero un nodo tenía una configuración de resolutor DNS obsoleta. Al reiniciar, la unidad de montaje falló. El servicio principal tenía RequiresMountsFor= apuntando a esa ruta porque alguien pensó que “estaba bien tenerlo”. El nodo no volvió limpiamente.

Como tenían el smoke reboot en canario, el problema se detectó antes de que el parche llegara a la flota. La corrección fue quirúrgica: el montaje de reporting se cambió a nofail y se eliminó el requisito de la unidad. El servicio principal no necesitaba el montaje para procesar pagos; lo necesitaba para emitir un informe. Esa distinción importa.

Sin heroísmos, sin war room. Solo una checklist, un canario y la insistencia en modelar dependencias como críticas vs opcionales. Lo aburrido ganó.

Errores comunes: síntoma → causa raíz → solución

Aquí es donde dejas de repetir el mismo incidente cada trimestre.

1) “Start request repeated too quickly”

Síntoma: La unidad falla con Result: start-limit-hit, no se reinicia.

Causa raíz: Bucle de crash o salida inmediata (config mala, binario faltante, conflicto de puerto). El throttle es systemd defendiendo el sistema.

Solución: Identifica por qué sale, arréglalo y luego systemctl reset-failed UNIT. Evita “solucionar” aumentando StartLimit a menos que te guste convertir un problema en muchos.

2) “Failed to run ‘start’ task: No such file or directory”

Síntoma: systemd no puede ejecutar el comando.

Causa raíz: Ruta errónea en ExecStart=, ejecutable faltante, arquitectura incorrecta o ExecStart sobrescrito en un drop-in que olvidaste.

Solución: systemctl cat UNIT, confirma el ExecStart final, verifica que el archivo exista y sea ejecutable. Luego daemon-reload si cambiaste archivos de unidad.

3) Servicio “active (exited)” pero funcionalidad ausente

Síntoma: systemctl muestra éxito, pero el servicio no está ejecutándose.

Causa raíz: Script Type=oneshot que sale; o unidad mal declarada donde el proceso principal hace fork y systemd lo pierde.

Solución: Usa el Type= correcto (simple, forking, notify, oneshot) y configura PIDFile= cuando sea necesario. Valida con systemctl show -p MainPID.

4) “Dependency failed for …” durante el arranque

Síntoma: Un target o servicio falla porque otra unidad falló.

Causa raíz: Requisito duro (Requires=) en un montaje/red que en realidad es opcional.

Solución: Reclasifica dependencias. Usa Wants= para componentes opcionales. Para montajes, considera nofail en fstab y/o elimina RequiresMountsFor= si no es realmente obligatorio.

5) “Permission denied” incluso como root

Síntoma: El servicio no puede leer claves/certs/configs.

Causa raíz: Denegación AppArmor o la unidad se ejecuta como un usuario no root y no tiene permisos de archivo.

Solución: Confirma el usuario efectivo (User=), propiedad de archivos y logs de AppArmor. Ajusta el perfil o reubica archivos a rutas esperadas.

6) Timeouts en servicios con respaldo de almacenamiento tras reinicio

Síntoma: Base de datos, cola o app hace timeout al arrancar; la cadena de montajes muestra esperas largas.

Causa raíz: fsck lento, demoras en montajes remotos, disco degradado o fstab incorrecto que causa reintentos.

Solución: Arregla montajes primero. Valida salud del dispositivo de bloque, tipo/opciones de sistema de archivos correctos y asegúrate de que los servicios no dependan de montajes opcionales.

7) Confusión “Unit file changed on disk”

Síntoma: Editaste una unidad y no surte efecto.

Causa raíz: Olvidaste systemctl daemon-reload, o editaste el archivo equivocado (unidad del paquete vs override).

Solución: Usa systemctl cat para ver la unidad final; recarga el daemon; reinicia la unidad.

Segundo chiste corto, y volvemos al trabajo: si depuras una unidad fallida reiniciando repetidamente, felicidades—has inventado chaos engineering, pero sin aprendizaje.

Listas de verificación / plan paso a paso

Checklist A: Cuando un servicio falla ahora mismo (incidente en vivo)

  1. Captura estado y logs (no mutar primero). Guarda systemctl status y journalctl -u UNIT -b.
  2. Clasifica la falla: exit-code, signal, timeout, dependencia, start-limit-hit.
  3. Confirma la definición final de la unidad con systemctl cat UNIT.
  4. Busca bloqueadores de dependencia: montajes, network-online, secretos, puertos.
  5. Decide la vía de restauración:
    • Si regresión de config: rollback de config.
    • Si regresión de paquete: rollback del paquete o pin de versión.
    • Si dependencia infra: permitir fallo (fail open) si es seguro o degradar con gracia.
  6. Estabiliza: detén unidades en flapping; desactiva timers; usa reset-failed después de eliminar la causa raíz.
  7. Verifica: endpoints de salud, sockets escuchando, petición sintética y systemctl is-active.

Checklist B: El triage de arranque “necesito el host de vuelta”

  1. Identifica las unidades que fallan:
cr0x@server:~$ systemctl --failed
  UNIT                LOAD   ACTIVE SUB    DESCRIPTION
● mnt-data.mount      loaded failed failed /mnt/data
● postgresql.service  loaded failed failed PostgreSQL RDBMS

LOAD   = Reflects whether the unit definition was properly loaded.
ACTIVE = The high-level unit activation state.
SUB    = The low-level unit activation state.

Significado: El fallo de montaje probablemente causa la falla de la base de datos.

Decisión: Arregla el montaje primero; luego reintenta la base de datos.

  1. Obtén contexto de ordenamiento:
cr0x@server:~$ systemd-analyze blame | head -n 10
1min 15.701s mnt-data.mount
12.233s cloud-init.service
8.614s systemd-fsck@dev-disk-by\x2duuid-3a1c...service
3.812s systemd-networkd-wait-online.service
2.115s snapd.service
1.998s apt-daily.service
1.233s systemd-resolved.service
1.101s ufw.service
981ms systemd-journald.service
742ms systemd-logind.service

Significado: El cuello de botella en el arranque es el montaje y wait-online. Esto guía tu próxima hora.

Decisión: Si es un problema de flota, no pierdas tiempo depurando la app; arregla almacenamiento y semántica de network-online.

Checklist C: Ediciones seguras de archivos de unidad (para no dejar inservible el host)

  1. Usa drop-ins: systemctl edit UNIT (no editar /lib/systemd/system directamente).
  2. Valida sintaxis y configuración fusionada: systemctl cat UNIT.
  3. Recarga el manager: systemctl daemon-reload.
  4. Reinicia: systemctl restart UNIT.
  5. Verifica logs y MainPID: systemctl status UNIT y systemctl show -p MainPID UNIT.
  6. Solo entonces habilita/deshabilita: systemctl enable --now UNIT si procede.

Checklist D: Triage consciente del almacenamiento (porque “Failed to start” a menudo es “el disco dijo no”)

  1. Revisa montajes: systemctl status *.mount para los fallidos.
  2. Correlaciona la cadena de arranque: systemd-analyze critical-chain SERVICE.
  3. Valida dispositivos: lsblk -f y blkid.
  4. Comprueba espacio y inodos:
cr0x@server:~$ df -h /
Filesystem      Size  Used Avail Use% Mounted on
/dev/sda2        20G   19G  200M  99% /

cr0x@server:~$ df -i /
Filesystem      Inodes   IUsed   IFree IUse% Mounted on
/dev/sda2      1310720 1310102     618  100% /

Significado: Puedes quedarte “sin inodos” teniendo aún espacio. Eso rompe logging, archivos PID, sockets—los servicios fallan de formas raras.

Decisión: Limpia directorios con muchos inodos (a menudo cache/temp), rota logs o expande el sistema de archivos. Luego reinicia servicios afectados.

Preguntas frecuentes

1) ¿Por qué systemctl dice “failed” pero el proceso en realidad está en ejecución?

Porque systemd rastrea lo que cree que es el “proceso principal”. Si el servicio hace fork inesperadamente, escribe un PID file incorrecto o usa el Type= equivocado, systemd puede perderlo. Comprueba con systemctl show -p MainPID UNIT y compáralo con ps. Corrige el Type= y el seguimiento de PID.

2) ¿Cuál es la diferencia entre “exit-code”, “signal” y “timeout” en Result?

exit-code significa que el proceso devolvió un valor distinto de cero. signal indica que fue terminado por una señal (OOM suele mostrar SIGKILL). timeout significa que systemd esperó más tiempo del permitido y lo terminó. Cada uno te lleva a un playbook distinto: errores de config/tiempo de ejecución vs kills por recursos vs demoras por dependencias/rendimiento.

3) ¿Por qué veo “Unit file changed on disk” después de editar un servicio?

Porque systemd no reparsea automáticamente archivos de unidad en cada inicio. Ejecuta systemctl daemon-reload y luego reinicia la unidad. Si estás editando unidades empaquetadas, deja de hacerlo y usa un drop-in.

4) ¿Cuál es la forma más rápida de encontrar el verdadero cuello de botella durante el arranque?

Usa systemd-analyze blame para localizar los sumideros de tiempo y luego systemd-analyze critical-chain SERVICE para ver la ruta de ordenamiento que importa para la unidad que te interesa.

5) ¿Debo aumentar StartLimitBurst y StartLimitIntervalSec para prevenir outages?

Rara vez. Los límites de inicio evitan que bucles de crash derritan hosts y dependencias aguas abajo. Si un servicio se cae inmediatamente, quieres que pare rápido y ruidoso. Arregla el crash, añade backoff en la app y usa políticas de reinicio sensatas.

6) ¿Cómo veo logs del arranque anterior?

Usa journalctl -b -1 para el arranque previo, y journalctl -u UNIT -b -1 para la unidad en ese arranque. Si no hay logs, journald puede no ser persistente en ese host.

7) ¿Cuándo debo usar “mask” vs “disable”?

disable evita que arranque en el boot o vía wants. Aún puede iniciarse manualmente o como dependencia. mask lo hace inarrancable (symlink a /dev/null). Usa mask para una unidad en flapping que debes impedir que arranque mientras estabilizas el sistema.

8) ¿Cómo confirmo si la falla se debe a permisos o AppArmor?

Los permisos aparecen como “permission denied” en logs de la aplicación y pueden verificarse con namei -l PATH y comprobaciones de propiedad. AppArmor muestra denegaciones de auditoría del kernel en el journal. Busca en el journal “apparmor=DENIED” y relaciona el perfil con el servicio.

9) ¿Por qué “network-online.target” ralentiza el arranque?

Porque systemd-networkd-wait-online.service (o el equivalente de NetworkManager) puede esperar a que las interfaces estén configuradas. Eso es útil para servicios que realmente necesitan red configurada, pero dañino si hiciste depender a todo de ello. Mantén dependencias estrechas.

10) ¿Qué debo hacer cuando la unidad falla por un archivo faltante bajo /etc?

Primero, decide si es un bug de despliegue o un conffile de paquete eliminado. Restaura desde gestión de config o backup, o revierte el cambio. Luego añade una guardia a nivel de unidad si procede (como ConditionPathExists=) para que la falla sea explícita y rápida.

Conclusión: pasos siguientes que puedes hacer hoy

“Failed to start …” no es un diagnóstico. Es la pistola de salida. Tu trabajo es convertirlo en una de pocas categorías concretas: fallo de exec, timeout, cascada de dependencias, permisos/AppArmor, presión de recursos o throttle de reinicio.

Haz estos pasos cuando no estés en llamas:

  1. Haz journald persistente en servidores donde la forense post-reinicio importe, para que las fallas no desaparezcan con el reinicio.
  2. Audita archivos de unidad personalizados para Type= correcto, dependencias explícitas y timeouts sensatos.
  3. Separa dependencias opcionales de las críticas (montajes, rutas de reporting, telemetría). Usa Wants= o degrada con gracia.
  4. Añade pruebas canarias de reinicio para cambios que toquen fstab, red, almacenamiento o unidades systemd. Los bugs de orden de arranque aman producción.
  5. Documenta los “tres primeros comandos” que tu equipo ejecuta (systemctl status, journalctl -u ... -b, systemctl cat) y haz que se cumpla en incidentes.

Systemd es determinista. Si lo tratas como una caja negra, parece una caja de gremlins. Usa el flujo de trabajo anterior y pasarás menos tiempo adivinando y más tiempo restaurando.

← Anterior
Ubuntu 24.04: Limitación de tasa en Nginx que no bloqueará a usuarios reales — cómo ajustarla
Siguiente →
Gráficas MCM: qué puede fallar y cómo los proveedores lo parchean

Deja un comentario