Despliegas un servicio. Falla. Arreglas lo obvio. Vuelve a fallar—más rápido. Entonces systemd escupe la temida línea: «Solicitud de inicio repetida demasiado rápido» y deja de intentarlo. Ahora estás atascado: no solo el servicio está caído, sino que tu sistema init se niega educadamente a permitir que sigas dándote contra la misma pared.
Esto no es que systemd sea “misterioso”. Está haciendo exactamente lo que fue diseñado para hacer: limitar la tasa de unidades que hacen flapping para que un servicio malo no convierta tu host en una estufa que escupe logs. El truco está en diagnosticar la falla real y luego aplicar arreglos que sobrevivan a actualizaciones, reinicios y al tú del futuro a las 2 a.m.
Qué significa realmente “Solicitud de inicio repetida demasiado rápido”
Systemd te está diciendo: “Intenté iniciar esta unidad varias veces en una ventana corta. Siguió fallando. He terminado hasta que intervengas.” La intervención puede ser una corrección de configuración, de dependencias o un cambio deliberado en la política de reinicio de la unidad. A veces también necesitas limpiar el estado de fallo.
En lo profundo, systemd aplica un límite de tasa de inicio por unidad. Si la unidad pasa a estado de fallo demasiadas veces dentro del intervalo configurado, systemd alcanza el límite de inicio y deja de reiniciarla automáticamente. El comportamiento por defecto varía según la versión y la política del distro, pero el mecanismo central es consistente: StartLimitIntervalSec + StartLimitBurst. Si alcanzas esa combinación, obtendrás el mensaje.
Nueva matización importante: esto no solo trata de Restart=. Incluso unidades que no se reinician por sí mismas pueden alcanzar el límite si algo (una dependencia, un timer, un administrador ejecutando repetidamente systemctl start) desencadena intentos frecuentes.
Qué deberías hacer: trata el mensaje como un síntoma de un bucle. El bucle es el problema. El limitador es el cinturón de seguridad.
Una cita, porque sigue vigente: “La esperanza no es una estrategia.” — Gene Kranz
Guía de diagnóstico rápido (primero/segundo/tercero)
Si quieres encontrar el cuello de botella rápidamente, no empieces editando archivos unit. Empieza respondiendo tres preguntas: ¿Qué falló? ¿Por qué falló? ¿Qué siguió intentando?
Primero: confirma que el limitador fue lo que detuvo los reintentos
- Busca resultados
start-limit-hity el código de salida real del proceso del servicio. - Confirma si el servicio sale inmediatamente (binario malo, config errónea) o se agota en tiempo (cuelgue, dependencia no disponible).
Segundo: encuentra el primer error real en los logs
- Usa
journalctl -ucon una ventana temporal ajustada. - Busca el fallo más temprano en la ráfaga, no el último. El último suele ser solo systemd rindiéndose.
Tercero: mapea el disparador del bucle
- ¿La unidad tiene
Restart=always? - ¿Es una cadena de dependencias donde un padre sigue intentándolo?
- ¿Un timer o path unit la está activando?
- ¿Algo externo ejecuta repetidamente
systemctl start(o un agente de gestión de configuración)?
Las victorias más rápidas suelen venir de: arreglar el error subyacente real y luego ajustar la semántica de reinicio para que una falla no se convierta en una denegación de servicio contra tu propio host.
Tareas prácticas: comandos, salidas, decisiones (12+)
A continuación hay tareas probadas en campo. Cada una incluye: un comando, cómo se ve una salida realista, qué significa y la decisión que deberías tomar a continuación.
Task 1: Check unit state and confirm start-limit-hit
cr0x@server:~$ systemctl status myapp.service
× myapp.service - MyApp API
Loaded: loaded (/lib/systemd/system/myapp.service; enabled; preset: enabled)
Drop-In: /etc/systemd/system/myapp.service.d
└─override.conf
Active: failed (Result: start-limit-hit) since Mon 2025-12-29 09:12:11 UTC; 2min 3s ago
Duration: 220ms
Process: 18934 ExecStart=/usr/local/bin/myapp --config /etc/myapp/config.yml (code=exited, status=1/FAILURE)
Main PID: 18934 (code=exited, status=1/FAILURE)
Dec 29 09:12:11 server systemd[1]: myapp.service: Scheduled restart job, restart counter is at 5.
Dec 29 09:12:11 server systemd[1]: myapp.service: Start request repeated too quickly.
Dec 29 09:12:11 server systemd[1]: myapp.service: Failed with result 'start-limit-hit'.
Dec 29 09:12:11 server systemd[1]: Failed to start myapp.service - MyApp API.
Significado: systemd limitó la tasa de la unidad. El servicio en sí sale con estado 1. El bucle de reinicios alcanzó 5 intentos (umbral típico de burst).
Decisión: no empieces por ajustar los límites de reinicio. Tienes una falla real de la aplicación. Ve a los logs para buscar el primer error.
Task 2: Pull logs around the failure window (and stop reading the last line only)
cr0x@server:~$ journalctl -u myapp.service -b --since "10 minutes ago" --no-pager
Dec 29 09:12:10 server myapp[18934]: ERROR: cannot read config file: /etc/myapp/config.yml: permission denied
Dec 29 09:12:10 server systemd[1]: myapp.service: Main process exited, code=exited, status=1/FAILURE
Dec 29 09:12:10 server systemd[1]: myapp.service: Failed with result 'exit-code'.
Dec 29 09:12:11 server systemd[1]: myapp.service: Scheduled restart job, restart counter is at 1.
Dec 29 09:12:11 server systemd[1]: Started myapp.service - MyApp API.
Dec 29 09:12:11 server myapp[18938]: ERROR: cannot read config file: /etc/myapp/config.yml: permission denied
Dec 29 09:12:11 server systemd[1]: myapp.service: Main process exited, code=exited, status=1/FAILURE
Dec 29 09:12:11 server systemd[1]: myapp.service: Start request repeated too quickly.
Significado: La falla real es un problema de permisos en el archivo de configuración. El limitador está aguas abajo.
Decisión: corrige la propiedad/permisos del archivo o el User/Group de la unidad. No aumentes el burst para “hacer que funcione”.
Task 3: Inspect the effective unit configuration (what systemd really runs)
cr0x@server:~$ systemctl cat myapp.service
# /lib/systemd/system/myapp.service
[Unit]
Description=MyApp API
After=network-online.target
Wants=network-online.target
[Service]
User=myapp
Group=myapp
ExecStart=/usr/local/bin/myapp --config /etc/myapp/config.yml
Restart=on-failure
RestartSec=1
[Install]
WantedBy=multi-user.target
# /etc/systemd/system/myapp.service.d/override.conf
[Service]
RestartSec=250ms
Significado: Hay un drop-in override que establece RestartSec=250ms. Eso hace que servicios inestables alcancen el limitador muy rápido.
Decisión: mantén el retraso de reinicio sensato (2–10 segundos) a menos que tengas una buena razón. Tus logs y discos te lo agradecerán.
Task 4: Identify start limit settings (defaults vs overridden)
cr0x@server:~$ systemctl show myapp.service -p StartLimitIntervalSec -p StartLimitBurst -p Restart -p RestartUSec
StartLimitIntervalSec=10s
StartLimitBurst=5
Restart=on-failure
RestartUSec=250ms
Significado: Con un intervalo de 10 segundos y burst de 5, un retraso de reinicio de 250ms puede disparar el límite casi de inmediato.
Decisión: arregla el error subyacente; luego establece RestartSec=2s o más a menos que el servicio sea seguro para reinicios agresivos.
Task 5: Reset the failed state (only after you made a change)
cr0x@server:~$ systemctl reset-failed myapp.service
cr0x@server:~$ systemctl start myapp.service
cr0x@server:~$ systemctl status myapp.service
● myapp.service - MyApp API
Loaded: loaded (/lib/systemd/system/myapp.service; enabled; preset: enabled)
Drop-In: /etc/systemd/system/myapp.service.d
└─override.conf
Active: active (running) since Mon 2025-12-29 09:16:42 UTC; 3s ago
Main PID: 19501 (myapp)
Tasks: 8
Memory: 34.2M
CPU: 210ms
CGroup: /system.slice/myapp.service
└─19501 /usr/local/bin/myapp --config /etc/myapp/config.yml
Significado: El límite de inicio había bloqueado los intentos; reset-failed lo limpió. El servicio ahora se ejecuta.
Decisión: si sigue fallando tras el reset, no has arreglado la causa raíz. Vuelve a los logs.
Task 6: Confirm the service user can read what it needs
cr0x@server:~$ sudo -u myapp test -r /etc/myapp/config.yml && echo OK || echo NO
NO
cr0x@server:~$ ls -l /etc/myapp/config.yml
-rw------- 1 root root 912 Dec 29 08:58 /etc/myapp/config.yml
Significado: La config es solo para root. Si el servicio se ejecuta como User=myapp, no puede leerla.
Decisión: cambia la propiedad/grupo, ajusta los permisos o ejecuta el servicio con otro usuario (último recurso).
Task 7: Spot dependency chain failures (the “it’s not my unit” problem)
cr0x@server:~$ systemctl list-dependencies --reverse myapp.service
myapp.service
● myapp.target
● multi-user.target
Significado: No mucho lo está llamando; probablemente está habilitado directamente.
Decisión: si la lista de dependencias inversas muestra una cadena mayor (como un target o otro servicio), puede que necesites arreglar la unidad padre también.
Task 8: Identify whether a timer or path unit is repeatedly triggering the service
cr0x@server:~$ systemctl list-timers --all | grep -E 'myapp|myapp-'
Mon 2025-12-29 09:20:00 UTC 2min 10s left Mon 2025-12-29 09:10:00 UTC 7min ago myapp-refresh.timer myapp-refresh.service
cr0x@server:~$ systemctl status myapp-refresh.timer
● myapp-refresh.timer - MyApp refresh timer
Loaded: loaded (/lib/systemd/system/myapp-refresh.timer; enabled; preset: enabled)
Active: active (waiting) since Mon 2025-12-29 08:10:00 UTC; 1h 10min ago
Trigger: Mon 2025-12-29 09:20:00 UTC; 2min 10s left
Significado: Otra unidad puede estar llamando a tu servicio (o a uno relacionado) regularmente. Un job de refresh que falla puede parecer que “myapp está haciendo flapping” si comparten recursos.
Decisión: si el timer es demasiado agresivo o está roto, arregla la unidad timer o desactívala hasta que se estabilice.
Task 9: Check exit codes and signal reasons across restarts
cr0x@server:~$ systemctl show myapp.service -p ExecMainStatus -p ExecMainCode -p ExecMainStartTimestamp -p ExecMainExitTimestamp
ExecMainStatus=1
ExecMainCode=exited
ExecMainStartTimestamp=Mon 2025-12-29 09:12:10 UTC
ExecMainExitTimestamp=Mon 2025-12-29 09:12:10 UTC
Significado: Sale inmediatamente. Eso suele ser config, permisos, archivos faltantes, librerías faltantes o “no puede enlazar el puerto”. No es una dependencia lenta.
Decisión: prioriza la validación de configuración y problemas del entorno; no pierdas tiempo aumentando TimeoutStartSec.
Task 10: Verify ports and socket activation assumptions
cr0x@server:~$ ss -lntp | grep ':8080'
LISTEN 0 4096 0.0.0.0:8080 0.0.0.0:* users:(("nginx",pid=1201,fd=6))
Significado: Algo más posee el puerto 8080 (nginx aquí). Si myapp espera enlazarlo, se bloqueará en un bucle de fallos.
Decisión: cambia el puerto de escucha de myapp, ajusta el proxy de nginx o usa la activación por socket de systemd correctamente (no a medias).
Task 11: Validate unit file syntax and hidden typos
cr0x@server:~$ systemd-analyze verify /etc/systemd/system/myapp.service
/etc/systemd/system/myapp.service:12: Unknown lvalue 'RestartSecs' in section 'Service'
Significado: Systemd ignoró una directiva mal escrita. Pensabas que tenías un retraso de reinicio; no lo tienes.
Decisión: corrige la errata, ejecuta daemon-reload y vuelve a comprobar systemctl show para los valores efectivos.
Task 12: Inspect environment seen by the service (PATH, vars, working dir)
cr0x@server:~$ systemctl show myapp.service -p Environment -p EnvironmentFile -p WorkingDirectory -p User -p Group
Environment=
EnvironmentFile=
WorkingDirectory=/
User=myapp
Group=myapp
Significado: No se establece entorno y el directorio de trabajo es root. Si tu app espera rutas relativas, puede fallar instantáneamente.
Decisión: establece WorkingDirectory= y Environment= o EnvironmentFile= explícitamente. Evita depender de los valores por defecto de un shell interactivo.
Task 13: Find who changed what (drop-ins, vendor units, overrides)
cr0x@server:~$ systemctl show myapp.service -p FragmentPath -p DropInPaths
FragmentPath=/lib/systemd/system/myapp.service
DropInPaths=/etc/systemd/system/myapp.service.d/override.conf
Significado: La unidad del proveedor está en /lib y tu override local está en /etc. Ese es el patrón correcto en Debian.
Decisión: nunca edites /lib/systemd/system/*.service directamente. Pon cambios en drop-ins para que persistan tras actualizaciones de paquetes.
Task 14: Track down “it works manually but not as a service”
cr0x@server:~$ sudo -u myapp /usr/local/bin/myapp --config /etc/myapp/config.yml
ERROR: cannot open database socket /run/postgresql/.s.PGSQL.5432: no such file or directory
Significado: Ejecutada como el usuario del servicio, no puede alcanzar una dependencia (socket de PostgreSQL). Quizá postgres no está en ejecución, escucha en otro sitio o los permisos lo bloquean.
Decisión: arregla la disponibilidad de la dependencia y la configuración; luego ajusta el orden de la unidad (After=) solo si es necesario.
Task 15: Visualize boot ordering and critical chain (slow starts and timeouts)
cr0x@server:~$ systemd-analyze critical-chain myapp.service
myapp.service +4.212s
└─network-online.target +4.198s
└─systemd-networkd-wait-online.service +4.150s
└─systemd-networkd.service +1.021s
└─systemd-udevd.service +452ms
Significado: Tu unidad está condicionada por network-online.target, que espera a que la red esté “online”. Eso puede estar bien—o ser una trampa de demora en el arranque.
Decisión: si tu servicio no necesita realmente “online”, cámbialo a After=network.target y elimina la dependencia wait-online. Menos sorpresas en el arranque.
Correciones de systemd que realmente perduran (y por qué)
Las “correcciones que perduran” tienen dos rasgos: sobreviven a actualizaciones y reflejan cómo se comporta el servicio en la vida real. La mayoría de servicios que hacen flapping están mal especificados (semántica de unidad equivocada) o rotos en tiempo de ejecución (config, permisos, dependencias). La respuesta correcta suele ser un pequeño drop-in override más una corrección real en el entorno de la aplicación.
Usa drop-ins, no edites en /lib
En Debian, los archivos unit gestionados por paquetes viven bajo /lib/systemd/system. Los cambios locales pertenecen a /etc/systemd/system. Si editas archivos en /lib, una actualización de paquete eventualmente “arreglará” tu arreglo.
cr0x@server:~$ sudo systemctl edit myapp.service
# (editor opens)
Añade un drop-in así:
cr0x@server:~$ cat /etc/systemd/system/myapp.service.d/override.conf
[Service]
Restart=on-failure
RestartSec=5s
TimeoutStartSec=30s
[Unit]
StartLimitIntervalSec=60s
StartLimitBurst=3
Por qué esto perdura: está en /etc, así que las actualizaciones no lo sobrescribirán. Además hace que tu servicio sea menos propenso a golpear el host: 5 segundos entre reintentos y solo 3 intentos por minuto antes de que systemd se detenga y fuerce la intervención humana.
Qué evitar: establecer StartLimitBurst=1000 o RestartSec=100ms porque “quieres que se recupere rápido.” Eso no es recuperación; es una bomba fork con mejor marca.
Haz que la lógica de reinicios coincida con los modos de fallo
La mayoría de servicios no necesitan Restart=always. Úsalo solo para demonios que deben estar presentes y que sean seguros de reiniciar sin importar el motivo de salida. Prefiere:
Restart=on-failurepara servidores típicos que pueden salir intencionalmente durante upgrades o recargas de config.Restart=nopara tareas one-shot que deben fallar de forma ruidosa y detenerse.Restart=on-abnormalsi quieres reinicios por señales o dumps, no por salidas limpias con código distinto de cero (útil en algunos patrones).
Arregla Type= y readiness, o systemd te “ayudará” a entrar en un bucle
Otro clásico: el servicio está bien, pero systemd piensa que nunca arrancó. Eso sucede cuando Type= está mal.
Type=simple(por defecto): el proceso arranca y permanece en primer plano. La mayoría de apps encajan aquí.Type=forking: demonios legados que se bifurcan al background. Si usas esto para una app en primer plano, systemd puede interpretarlo mal y matarla/reiniciarla.Type=notify: la app debe llamar a sd_notify. Si no lo hace, systemd puede agotar el tiempo y reiniciarla.Type=oneshot: tareas que se ejecutan y salen; combina conRemainAfterExit=yescuando proceda.
Establece el tipo correctamente. Si no controlas la app y no soporta sd_notify, no finjas que lo hace.
Usa ExecStartPre para validación, pero no lo conviertas en arma
ExecStartPre= es un buen lugar para validar la config antes de gastar un intento de inicio. Bien hecho, evita el flapping. Mal hecho, lo crea.
Ejemplo: valida que un archivo de config exista y sea legible por el usuario del servicio:
cr0x@server:~$ cat /etc/systemd/system/myapp.service.d/validate.conf
[Service]
ExecStartPre=/usr/bin/test -r /etc/myapp/config.yml
Si la prueba falla, la unidad falla rápido con una razón clara. Tu política de reinicio debería ser conservadora aquí. No quieres que el host martillee el mismo archivo faltante 20 veces por segundo.
Haz dependencias explícitas, pero no serialices en exceso el arranque
Añadir After=postgresql.service puede ayudar si tu app realmente no puede arrancar hasta que la BD esté arriba. Pero muchos equipos hacen cultos de “esperar a network-online” y “esperar por todo”. Entonces un DHCP lento tumba la mitad del arranque.
Una postura práctica:
- Usa
Wants=para dependencias suaves que te gustaría tener pero pueden degradar. - Usa
Requires=para dependencias duras. Pero ten en cuenta que si la dependencia falla, tu unidad también falla. - Prefiere que tu app haga sus propios reintentos para upstreams (BD, APIs) mientras systemd la mantiene en ejecución, en lugar de que systemd la reinicie constantemente.
Broma #1: Los bucles de reinicio son como las reuniones generales corporativas—mucho movimiento, ningún progreso, y todos salen más cansados.
Sabe cuándo ajustar StartLimit* (y cuándo no)
Hay casos legítimos para cambiar los límites de inicio:
- Servicios que pueden fallar brevemente durante mantenimiento upstream y pueden reintentar con seguridad durante minutos.
- Agentes que se conectan a endpoints remotos y ocasionalmente compiten al arrancar.
Pero cambiar los límites no arregla “permiso denegado” o “config inválida”. En esos casos, aumentar reintentos solo convierte un error en churn.
Usa systemd-run para reproducir de forma segura
Si necesitas reproducir el entorno del servicio sin editar la unidad real, systemd-run puede crear unidades transitorias con restricciones similares. Es una buena forma de probar suposiciones sobre usuario, directorio de trabajo y entorno.
cr0x@server:~$ sudo systemd-run --unit=myapp-test --property=User=myapp --property=WorkingDirectory=/var/lib/myapp /usr/local/bin/myapp --config /etc/myapp/config.yml
Running as unit: myapp-test.service
cr0x@server:~$ systemctl status myapp-test.service
● myapp-test.service - /usr/local/bin/myapp --config /etc/myapp/config.yml
Loaded: loaded (/run/systemd/transient/myapp-test.service; transient)
Active: failed (Result: exit-code) since Mon 2025-12-29 09:22:06 UTC; 1s ago
Process: 20411 ExecStart=/usr/local/bin/myapp --config /etc/myapp/config.yml (code=exited, status=1/FAILURE)
Decisión: si falla igual, no es “magia de systemd”. Es el entorno de ejecución.
Modos de fallo típicos detrás del mensaje
Esto es lo que veo más a menudo en flotas Debian. El mensaje del limitador es solo el portero. Estos son los invitados borrachos.
1) Desajustes de permisos y propiedad
El servicio se ejecuta como usuario no root. Archivos de config, sockets o directorios de estado son root-owned. El binario sale instantáneamente con un error útil, pero la unidad se reinicia demasiado rápido, alcanza los límites y ahora miras la línea equivocada.
2) Tipo de servicio equivocado
Programa en primer plano etiquetado como forking, o notify sin soporte de notificación. Systemd lo interpreta como no iniciado, agota el tiempo, lo mata, lo reinicia, repite.
3) Crash rápido por dependencia faltante
Ejemplo: socket de base de datos faltante en el arranque. La app sale rápido; systemd la reinicia; repetir. Arregla el orden o haz que la app tolere la indisponibilidad de la dependencia.
4) Fallos de bind
Puerto ya en uso, o puerto privilegiado sin la capacidad adecuada, o dirección de escucha equivocada. Estos fallan al instante y hacen bucles si RestartSec es pequeño.
5) ExecStart apunta a script wrapper con shebang malo
/bin/bash puede no existir en contenedores mínimos. O el script usa set -e y aborta por una variable de entorno faltante. Systemd lo reiniciará hasta que se canse.
6) Timeouts y arranques lentos
El proceso arranca pero no se vuelve “ready” antes de TimeoutStartSec. A menudo emparejado con Type=notify mal configurado.
7) Gestión de configuración peleando contigo
“Arreglas” el override manualmente y 5 minutos después un agente de gestión lo revierte. La unidad vuelve a hacer flapping y alcanza los límites de inicio. Enhorabuena, descubriste la mano invisible del cumplimiento.
Errores comunes: síntoma → causa raíz → arreglo
Esta sección pretende cambiar decisiones. Si reconoces un síntoma, salta la autocrítica y ve directo al arreglo real.
Síntoma: “Solicitud de inicio repetida demasiado rápido” después de poner Restart=always
Causa raíz: Enmascaraste una falla real con reinicios agresivos. La app está saliendo por una razón—config, permiso, dependencia, crash.
Arreglo: cambia a Restart=on-failure, pon RestartSec=5s, luego localiza el primer error en journalctl -u. Solo revisa los límites después de que el servicio sea estable.
Síntoma: El servicio arranca manualmente pero falla bajo systemd
Causa raíz: entorno diferente: directorio de trabajo, PATH, variables de entorno, permisos de usuario o configuración de shell interactivo faltante.
Arreglo: revisa systemctl show -p WorkingDirectory -p Environment, establécelos explícitamente y prueba con sudo -u.
Síntoma: La unidad “se agota” y luego alcanza el límite de inicio
Causa raíz: Type= incorrecto (notify sin notify) o readiness de dependencia lenta; systemd la mata tras TimeoutStartSec.
Arreglo: establece Type=simple salvo que sepas lo contrario, o implementa notify correctamente; ajusta TimeoutStartSec solo tras confirmar que es realmente lento y no bloqueado.
Síntoma: “Failed to start” con exit-code, pero sin logs útiles de la app
Causa raíz: stdout/stderr no llega al journal (logging custom), o el proceso falla antes de que su logger se inicialice; a veces ExecStart apunta al archivo equivocado.
Arreglo: confirma que ExecStart existe y es ejecutable; añade temporalmente StandardOutput=journal y StandardError=journal en un drop-in; verifica con systemctl cat.
Síntoma: El arreglo funciona hasta el reinicio, luego vuelve a hacer flapping
Causa raíz: directorios de estado en /run no creados, o tmpfiles faltantes; o dependencias de orden que dependen del timing de arranque.
Arreglo: usa RuntimeDirectory= o StateDirectory= en la unidad; asegura permisos vía systemd en vez de scripts ad-hoc.
Síntoma: Start limit hit solo durante despliegues
Causa raíz: scripts de despliegue reiniciando repetidamente mientras reemplazan binarios/configs, causando fallos transitorios en bucles cerrados.
Arreglo: coordina los pasos del despliegue: para la unidad, reemplaza artefactos, valida config y luego inicia solo una vez. Considera ExecReload donde aplique.
Síntoma: La unidad muestra “start-limit-hit” incluso después de arreglar la app
Causa raíz: systemd recuerda el estado de fallo.
Arreglo: ejecuta systemctl reset-failed myapp.service y luego inicia de nuevo. Si vuelve a alcanzarlo, no estás arreglado.
Tres mini-historias corporativas desde el frente
Mini-historia 1: El incidente causado por una suposición equivocada
Tenían un servicio API basado en Debian detrás de un balanceador. Tras un reinicio de mantenimiento rutinario, la mitad del pool volvió “unhealthy”. Los dashboards sonaban, pero no ayudaban: los hosts estaban arriba, la red parecía bien y systemd escupía «Solicitud de inicio repetida demasiado rápido» como si estuviera aburrido del asunto.
La suposición on-call fue clásica: “network-online.target está inestable otra vez.” Así que hicieron lo que la gente hace bajo estrés—alargaron la espera. Alguien aumentó TimeoutStartSec a unos minutos y añadió más líneas After=, serializando el arranque detrás de targets extra.
La causa real fue aburrida: el servicio se ejecutaba como User=api, y un paso post-instalación de paquete había reescrito el archivo de configuración con permisos solo para root. La app salía instantáneamente con “permission denied”, luego systemd hizo su bucle de reinicios hasta que el limitador de inicio entró en acción. Nada que ver con la red. Todo que ver con los modos de archivo.
Una vez que comprobaron journalctl -u por el primer error y corrieron sudo -u api test -r, quedó claro. Arreglaron la propiedad y añadieron un paso de validación duro en CI para asegurar permisos de archivos. El “problema de systemd” desapareció, porque nunca fue un problema de systemd.
También quitaron las dependencias de arranque adicionales que añadieron en pánico. Esa limpieza importó: redujo el tiempo de arranque y previno outages no relacionados más tarde. La lección no fue “no asumir.” Eso es demasiado genérico. La lección fue: asume primero las cosas aburridas—permisos, rutas, puertos—antes de construir una mitología alrededor de targets y ordenamientos.
Mini-historia 2: La optimización que salió mal
Un equipo de plataforma quería recuperación más rápida de fallos transitorios. Su razonamiento sonaba ordenado: si un servicio muere, reinícialo inmediatamente. Así que pusieron Restart=always y ajustaron RestartSec=200ms en una flota de microservicios internos. También aumentaron StartLimitBurst porque “no queremos que systemd se rinda”.
Durante un par de semanas parecía bien. Luego una dependencia empezó a devolver respuestas malformadas por un mal despliegue. Un servicio cliente se segfaultó al parsearlas. Con la nueva política, no solo hacía crash—disparaba reinicios a máquina. Cada reinicio recargaba configuración, abría conexiones, escribía logs y removía caches de CPU. Los hosts no murieron al instante, pero la latencia subió. No porque el servicio estuviera caído, sino porque hacía flapping lo suficiente como para asfixiar a sus vecinos.
Habían sustituido efectivamente “un crash” por “una denegación de servicio sostenida contra sus propios nodos”. Peor aún, el aumento de StartLimitBurst significó que systemd siguió participando en el caos por más tiempo. El radio de explosión creció de un cliente roto a un clúster ruidoso.
El arreglo no fue heroico. Revirtieron las configuraciones agresivas de reinicio, restauraron límites de inicio conservadores y añadieron backoff a nivel de aplicación cuando los datos upstream parecían erróneos. Luego usaron Restart=on-failure más RestartSec=5s para la mayoría de servicios, reservando reinicios rápidos para unos pocos demonios sin estado y bien comportados.
Mantuvieron una “optimización”: una corta validación ExecStartPre para que una mala config falle una vez y se quede fallada. Ese es el tipo de fallo rápido que quieres—rápido de detectar, no rápido de repetir.
Mini-historia 3: La práctica aburrida pero correcta que salvó el día
Un demonio adyacente al almacenamiento era responsable de montar y chequear volúmenes cifrados antes de que los servicios dependientes arrancaran. No era glamoroso. Pero era fundamental. El equipo lo implementó con tres hábitos poco sexy: drop-ins de unidad en /etc, StateDirectory= y RuntimeDirectory= explícitos, y una verificación corta de salud que registraba claramente en el journal.
Una mañana, tras una actualización del SO, un subconjunto de hosts empezó a fallar en el paso de preparación de volúmenes. Los servicios dependientes mostraban “Solicitud de inicio repetida demasiado rápido”, porque su montaje requerido nunca apareció. La diferencia fue que los logs de la unidad de preparación eran limpios y específicos: faltaba un módulo de kernel en ese subconjunto de hosts.
Porque las dependencias estaban modeladas con Requires= y orden claro, el modo de fallo fue controlado. En lugar de cada servicio dependiente haciendo flapping, fallaron rápido y se quedaron abajo. Eso suena mal hasta que has visto la alternativa: una manada atronadora de reinicios que entierra la causa raíz bajo ruido.
El arreglo fue sencillo: instalar el paquete del módulo faltante y reconstruir initramfs en los nodos afectados. La recuperación fue igual de sencilla: systemctl reset-failed en los targets y arrancarlos. Sin rarezas, sin ediciones misteriosas en unidades de proveedor, sin symlinks “temporales” viviendo en /lib.
Broma #2: El mejor truco SRE es hacer que los outages sean aburridos—porque la emoción no es más que downtime con mejor marketing.
Listas de verificación / plan paso a paso
Paso a paso: estabilizar una unidad que hace flapping sin ocultar el problema
- Congela el bucle: si está hammering el nodo, páralo.
cr0x@server:~$ sudo systemctl stop myapp.serviceDecisión: si detenerlo mejora la carga del sistema de inmediato, estabas en una tormenta de reinicios. Déjalo parado mientras depuras.
- Obtén el error real: lee logs por el primer fallo.
cr0x@server:~$ journalctl -u myapp.service -b --since "30 minutes ago" --no-pager | head -n 60 Dec 29 09:12:10 server myapp[18934]: ERROR: cannot read config file: /etc/myapp/config.yml: permission denied Dec 29 09:12:10 server systemd[1]: myapp.service: Main process exited, code=exited, status=1/FAILURE ...Decisión: si ves un error claro de la app, arréglalo primero. Si los logs están vacíos, valida ExecStart y habilita salida al journal temporalmente.
- Confirma la configuración efectiva de la unidad:
cr0x@server:~$ systemctl show myapp.service -p FragmentPath -p DropInPaths -p User -p Group -p ExecStart -p Type -p Restart -p RestartUSec FragmentPath=/lib/systemd/system/myapp.service DropInPaths=/etc/systemd/system/myapp.service.d/override.conf User=myapp Group=myapp ExecStart={ path=/usr/local/bin/myapp ; argv[]=/usr/local/bin/myapp --config /etc/myapp/config.yml ; ... } Type=simple Restart=on-failure RestartUSec=250msDecisión: si RestartSec es demasiado pequeño, arréglalo en un override. Si Type parece incorrecto, corrígelo. Si User/Group no coincide con la propiedad de archivos, corrige permisos o la unidad.
- Aplica un override duradero vía drop-in:
cr0x@server:~$ sudo systemctl edit myapp.serviceUsa ajustes como
RestartSec=5sy límites de inicio conservadores mientras estabilizas. - Recarga systemd y resetea el fallo:
cr0x@server:~$ sudo systemctl daemon-reload cr0x@server:~$ sudo systemctl reset-failed myapp.serviceDecisión: si olvidas daemon-reload, tus ediciones pueden no aplicarse. Si olvidas reset-failed, systemd puede seguir negándose a iniciar.
- Inicia una vez, observa:
cr0x@server:~$ sudo systemctl start myapp.service cr0x@server:~$ systemctl status myapp.service --no-pager ● myapp.service - MyApp API Active: active (running) since Mon 2025-12-29 09:30:01 UTC; 2s agoDecisión: si falla otra vez, no sigas reiniciando manualmente. Vuelve a los logs; itera con intención.
Checklist: “¿Merece esta unidad reiniciarse en absoluto?”
- Si es un job por lotes: usa
Type=oneshot, consideraRestart=noy falla en voz alta. - Si es un daemon que debería estar presente:
Restart=on-failuresuele ser correcto. - Si sale limpiamente como parte de su operación normal: evita
Restart=alwayso crearás un bucle por diseño. - Si depende de red o servicios upstream: favorece reintentos/backoff a nivel de aplicación mientras el proceso se mantiene en ejecución.
Checklist: valores por defecto seguros para la mayoría de servicios internos
Restart=on-failureRestartSec=5s(2s para demonios muy ligeros sin estado; 10s para los pesados)StartLimitIntervalSec=60sStartLimitBurst=3(5 también está bien; elige algo que fuerce atención humana)- Explícito
User=,Group= WorkingDirectory=si la app usa rutas relativasRuntimeDirectory=/StateDirectory=para directorios que el servicio necesita
Datos e historia interesantes (edición limitación de inicio de systemd)
- Systemd no inventó los bucles de reinicio; solo los hizo más fáciles de expresar con
Restart=y más visibles con el estado estructurado de las unidades. - El “límite de inicio” es por unidad, no global. Un servicio que hace flapping puede ser contenido sin castigar a otros—a menos que asfixie la máquina.
- Directorios drop-in (
/etc/systemd/system/UNIT.d/*.conf) existen específicamente para que las actualizaciones de paquetes no sobrescriban la intención operativa local. - La separación de Debian entre /lib y /etc es una decisión de política deliberada: archivos del proveedor en
/lib, cambios del administrador en/etc. - El journal de systemd fue diseñado para capturar metadatos estructurados (nombre de unidad, PID, cgroup) para que puedas responder “¿qué pasó?” sin arqueología de grep.
- La limitación de inicio es una característica de seguridad que previene inundaciones de logs y churn de CPU; es básicamente un disyuntor para la supervisión de procesos.
- Type=notify vino del deseo de reemplazar “sleep 5 y ojalá” por señalización explícita de readiness—genial cuando se usa bien, punitivo cuando se finge.
- Network-online.target se malentiende con frecuencia: no es “la red existe”, es “un componente declara que la red está configurada”, lo cual puede ser lento o incorrecto según la pila.
- Resetear unidades falladas es una acción explícita del operador porque systemd trata la falla repetida como un estado significativo, no como un glitch transitorio para ignorar.
Preguntas frecuentes
1) ¿Significa “Solicitud de inicio repetida demasiado rápido” que systemd está roto?
No. Significa que tu servicio falló repetidamente y systemd aplicó su límite de tasa configurado. El servicio está roto (o mal especificado) y systemd está previniendo churn infinito.
2) ¿Cómo limpio el límite de inicio para poder intentar de nuevo?
Después de haber hecho un arreglo real, ejecuta:
cr0x@server:~$ sudo systemctl reset-failed myapp.service
cr0x@server:~$ sudo systemctl start myapp.service
Si vuelve a alcanzar el límite inmediatamente, no solucionaste la causa raíz.
3) ¿Debería aumentar StartLimitBurst para hacerlo más “resiliente”?
Normalmente no. Aumentar burst oculta fallos reales y aumenta el churn del host. Prefiere arreglar el error real y usar un RestartSec sensato. Si aumentas el burst, hazlo modestamente e intencionalmente.
4) ¿Cuál es la diferencia entre RestartSec y StartLimitIntervalSec?
RestartSec es la demora entre intentos de reinicio. StartLimitIntervalSec es la ventana sobre la que systemd cuenta los inicios fallidos, y StartLimitBurst es el máximo de intentos permitidos en esa ventana.
5) ¿Por qué falla solo en el arranque pero funciona si lo inicio más tarde?
El timing del arranque expone problemas de dependencias: montajes faltantes, red no disponible, BD no lista, directorios runtime no creados. Modela la dependencia (o haz que la app la tolere) y evita abusar de network-online.target.
6) Edité la unidad pero no cambió nada. ¿Por qué?
Causas comunes: editaste la unidad del proveedor bajo /lib y la reemplazaron, olvidaste systemctl daemon-reload, o tu ajuste tenía una errata y fue ignorado. Ejecuta systemd-analyze verify y systemctl show para confirmar los valores efectivos.
7) ¿Está bien poner Restart=always para servicios críticos?
A veces sí. Pero solo si entiendes el comportamiento de salida del servicio y es seguro reiniciarlo incondicionalmente. Muchos servicios salen intencionalmente durante upgrades o cambios de config; Restart=always puede pelear con esos flujos.
8) ¿Cómo puedo saber si un timer u otra cosa sigue provocando inicios?
Revisa timers y dependencias inversas:
cr0x@server:~$ systemctl list-timers --all
cr0x@server:~$ systemctl list-dependencies --reverse myapp.service
Si un timer dispara un job cada minuto, puede que veas intentos de inicio repetidos que no provienen de Restart=.
9) ¿Cuándo debería cambiar TimeoutStartSec?
Sólo cuando hayas confirmado que el servicio es legítimamente lento para estar listo. Si la app sale inmediatamente, aumentar el timeout no hace nada. Si Type=notify está mal, estás arreglando la capa equivocada.
10) ¿Puedo hacer que systemd registre más sobre por qué dejó de intentar?
Systemd ya registra el evento de start-limit. La pieza que falta suele ser la salida de error de la propia app. Asegúrate de que escriba a stderr/stdout o configura logging para que journalctl -u capture la razón del primer fallo.
Conclusión: siguientes pasos para no volver al zanjón
“Solicitud de inicio repetida demasiado rápido” es una amabilidad. Es systemd diciéndote que el servicio está fallando en un bucle cerrado y no va a ayudarte a quemar la máquina. Trátalo como un checkpoint, no como la causa raíz.
Siguientes pasos que realmente funcionan en entornos Debian 13:
- Encuentra el primer error real con
journalctl -u UNITy deja de leer solo la última línea. - Inspecciona la configuración efectiva con
systemctl showysystemctl cat. Confía en lo que systemd ejecuta, no en lo que crees que escribiste. - Arregla el problema de ejecución (permisos, puertos, dependencias, directorio de trabajo) antes de tocar los límites.
- Aplica cambios duraderos con drop-ins bajo
/etc/systemd/system/UNIT.d/, luegodaemon-reload. - Establece semánticas de reinicio sensatas:
Restart=on-failure,RestartSecrazonable,StartLimit*conservadores. - Resetea el estado de fallo solo después de cambios:
systemctl reset-failed.
Si haces solo una cosa: deja de tratar a systemd como una tragamonedas. Tira menos de la palanca. Lee más los logs. El servicio o se iniciará—o fallará por una razón que puedas arreglar.