SYSTEMD · 2026-11-02

systemd muestra el motivo del reinicio de un servicio

El motivo por el que un servicio de systemd se reinició: status se encoge de hombros, el journal confiesa. Cinco muertes en vivo — y la única que llama éxito.

HUMANpor @mrkeepalive55m8 min de lectura

Matamos un servicio de cinco formas distintas en una VM Arch desechable y dejamos que systemd se interrogara a sí mismo. Cada línea de abajo es la salida propia del journal, impresa sin editar — cuatro confesiones, un silencio y un «se rindió» que la salida de status nunca menciona. Si llegaste desde nuestra autopsia anterior de servicios que se niegan a arrancar, esta es la habitación después del portazo: el servicio arrancó, corrió, murió, y quieres saber quién lo hizo.

el procesomuere de algún modo PID 1 escribe lalínea de confesióncode= · status= · result= Restart= decidetras RestartSec reinicio programado,contador en N start-limit-hit:deja de intentarlo muerte por SIGTERM:ni una línea
Fig 1 — quién escribe qué cuando muere un servicio. La línea de confesión es la única parte que no controlas en nada; aterriza en el journal antes de que se dispare cualquier política.

¿Cómo muestro el motivo por el que un servicio de systemd se reinició?

Tres comandos, en este orden. El primero lee la cinta, el segundo lee el contador, el tercero filtra la cinta hasta dejar solo las muertes.

# the confession, most recent first
journalctl -u myservice.service -n 50 --no-pager

# the counter and the last death, machine-readable
systemctl show myservice.service -p NRestarts -p ExecMainStatus \
  -p ExecMainCode -p Result

# every death, timestamped to the millisecond
journalctl -u myservice.service --grep 'Main process exited' \
  -o short-precise | tail -10

Verificación: en nuestra VM de banco, el segundo comando respondió Result=exit-code, NRestarts=3, ExecMainStatus=1, ExecMainCode=1 para un servicio que había muerto tres veces segundos antes. Last tested 2026-11-02.

El que casi nadie ve es NRestarts. systemctl status reescribe su relato en cada reinicio, así que siempre parece que el servicio «acaba de arrancar». El contador no se reinicia. En el banco, status reportó active (running), con 10 ms de vida — mientras el contador, una línea más abajo, decía restart counter is at 3. Esa brecha es la sesión de depuración entera: el servicio tiene antecedentes penales, y status muestra su acta de nacimiento.

¿Por qué systemctl status no muestra por qué se reinició el servicio?

Porque status es una foto del ahora, y el journal es la cinta de la cámara de vigilancia. Aquí está la foto, impresa desde el banco mientras el servicio moría cada ~1.2 segundos:

status, en pleno bucle — el encogimiento de hombros
● flaky.service - dies on purpose for the journal
     Loaded: loaded (/etc/systemd/system/flaky.service; static)
     Active: active (running) since Mon 2026-11-02 05:05:46 UTC; 10ms ago
   Main PID: 627 ((die.sh))
      CPU: 3ms

Nov 02 05:05:46 bench-arch systemd[1]: flaky.service: Scheduled restart
  job, restart counter is at 3.

status es una foto del ahora; el journal es la cinta de la cámara de vigilancia.

¿Qué significan code=exited y status=11/SEGV en el journal?

Cada muerte produce dos líneas en el journal — la línea de código y el veredicto. Provocamos cinco clases a propósito. Estas son las líneas de confesión, sin editar:

la galería de confesiones — una por muerte, directa del banco
# death 1: the app exits nonzero
flaky.service: Main process exited, code=exited, status=1/FAILURE
flaky.service: Failed with result 'exit-code'.

# death 2: the app segfaults (kill -SEGV)
flaky.service: Main process exited, code=dumped, status=11/SEGV

# death 3: the app aborts (kill -ABRT) — assert died, or a
# watchdog inside the app pulled the plug
flaky.service: Main process exited, code=dumped, status=6/ABRT

# death 4: ExecStart points at a file that does not exist —
# the app never ran
(nothere)[1260]: flaky.service: Failed at step EXEC spawning
  /usr/local/bin/nothere: No such file or directory
flaky.service: Main process exited, code=exited, status=203/EXEC

# death 5: the app kills itself with SIGTERM (kill -TERM)
flaky.service: Deactivated successfully.

Regla de decodificación: code= te dice cómo se fue (exited = salió por su pie, dumped = lo sacaron en camilla), status= nombra la manera de la muerte, y Failed with result es el resumen de una línea de systemd. La tabla es el decodificador completo que nos topamos:

El journal diceQué pasó en realidadDónde mirar después
code=exited, status=1/FAILUREla app eligió morir — config mala, puerto ocupado, env faltantelos logs de la app, arriba de la salida, en journalctl -u NAME
code=dumped, status=11/SEGVsegfault — bug de memoria en la app o en una dependencia nativacoredumpctl list para el backtrace
code=dumped, status=6/ABRTabort — un assert fallido, o un watchdog interno se rindióel log de la propia app, una línea antes del abort
status=203/EXECruta de ExecStart mala o no ejecutable — la app nunca arrancórevisa la ruta, chmod +x, el shebang del intérprete
Deactivated successfully (sin línea de fallo)murió por SIGTERM — contado como salida limpia por defecto§ de abajo; este es el fantasma
start request repeated too quickly / start-limit-hitsystemd se rindió a reiniciar — saltó el límite de ráfagaarregla la muerte primero; solo entonces toca el límite

¿Por qué mi servicio de systemd se reinicia cada pocos segundos?

Porque Restart= hace su trabajo y la muerte nunca se detuvo. La cadencia la fija RestartSec (por defecto 100 ms) y la paciencia la ponen StartLimitIntervalSec/StartLimitBurst (por defecto 5 fallos en 10 s). En el banco, con RestartSec=1, tres muertes aterrizaron así:

cadencia — RestartSec=1, tres muertes en tres segundos
05:05:43 flaky.service: Scheduled restart job, restart counter is at 1.
05:05:44 flaky.service: Scheduled restart job, restart counter is at 2.
05:05:46 flaky.service: Scheduled restart job, restart counter is at 3.

# and when the burst limit applies, the loop ends like this:
flaky.service: Start request repeated too quickly.
flaky.service: Failed with result 'start-limit-hit'.

Mide la tasa de tu propio bucle en una línea:

# restarts in the last hour, with timestamps
journalctl -u myservice.service --since "1 hour ago" \
  --grep 'Scheduled restart' --no-pager | tail -20

Verificación: en el banco esto imprimió las tres líneas de arriba; el contador volvió a 1 cuando la ventana de límite de 10 s completó su ciclo. Last tested 2026-11-02.

La trampa de la mordaza merece su propia advertencia: subir StartLimitBurst no arregla un servicio que muere, esconde el cadáver más seguido. Arregla la muerte — la tabla decodificadora de arriba dice dónde mirar — y trata el límite como lo que es: un detector de humo.

¿Por qué mi servicio se detuvo y el journal dice éxito?

Este es el que no esperábamos, y es la razón por la que existe el post. La muerte 5 fue el proceso matándose a sí mismo con SIGTERM. Ningún reinicio siguió. No apareció ninguna línea de fallo. Todo el comentario del journal fue Deactivated successfully, y systemctl show respondió Result=success, ExecMainCode=0, ExecMainStatus=0 — un asesinato reportado como un retiro limpio.

La causa está en la man page, no en el journal: la lista por defecto de SuccessExitStatus incluye SIGTERM (y SIGINT, SIGPIPE, status 0). Cualquier proceso que muera por SIGTERM es, para la política de reinicio, un éxito — así que Restart=on-failure nunca se dispara, y nada en el log la llama muerte. Si tu servicio está supervisado o lo termina un watchdog externo, este es el silencio que llevas mirando.

Dos salidas honestas, según lo que quieras:

El método del banco, para que ejecutes las cinco muertes tú mismo

VM Arch desechable (quickemu, 2 vCPU, sin cambios en el host). Una unidad, un script: /usr/local/bin/die.sh recibe una clase de muerte como argv — exit 1, kill -SEGV $$, kill -ABRT $$, kill -TERM $$, o un ExecStart apuntando a una ruta inexistente para el 203. Cambia el ExecStart de la unidad entre muertes, daemon-reload, arranca, deja que muera tres veces, detén, captura. Log de recibos completo, sin editar: cinco confesiones, el encogimiento de hombros, la cadencia, la rendición y el fantasma — las líneas del journal impresas arriba salen verbatim de él. Máquina solo CPU, nada GPU-adyacente, nada preservado: el clon se destruyó en la misma sesión.

El recuento honesto: no decodificamos coredumps (coredumpctl es una tarde entera por sí solo), y los nombres del banco son falsos — tus líneas de journalctl nombrarán tus unidades. Lo que se sostuvo: cuatro de cinco muertes confiesan con code=, status= y una línea de resultado, el contador nunca olvida, y la quinta muerte — la de SIGTERM — el journal la llama éxito. El silencio del journal es en sí una confesión. status seguirá encogiéndose de hombros; lee la cinta.