¿El servicio de systemd no arranca? Cómo arreglarlo

¿Servicio de systemd que no arranca? Lee journalctl bien, corrige las cinco causas detrás de la mayoría de fallos y devuelve la unidad a active en minutos.

HUMANpor @mrsaynothing42d5 min de lectura

Un servicio de systemd que no arranca casi nunca es un misterio. Ejecuta systemctl status <unit> y luego lee las últimas 50 líneas del journal de esa unidad con journalctl -u <unit> -n 50 --no-pager — entre los dos, normalmente una de cinco causas se nombra a las claras: una ruta incorrecta, un binario que falta, permisos equivocados, una denegación de SELinux/AppArmor o un error de sintaxis en el unit file. El motivo del fallo está en el log; los arreglos de abajo son puro pattern matching contra él.

¿Cómo veo por qué falló un servicio de systemd?

Primero status, después el journal:

systemctl status myapp.service
journalctl -u myapp.service -n 50 --no-pager

status te da el estado (inactive (dead), failed (exit-code), activating (auto-restart)) y las últimas líneas de log. El journal te da la historia completa: stdout, stderr y las quejas propias de systemd sobre la unidad.

Si la unidad falló antes y quieres el registro de esa corrida, añade -b para el arranque actual o --since today:

journalctl -u myapp.service -b --no-pager

Para el toolkit completo — boots, prioridades, seguir la salida en vivo — mira el cheat sheet de journalctl. Es la misma memoria muscular.

Otro par que vale la pena conocer:

systemctl list-units --failed
systemctl reset-failed myapp.service

El primero lista cada unidad en rojo de la máquina. El segundo limpia el estado de fallo después de que ya lo arreglaste — cosmético, pero evita que las páginas de status den falsas alarmas.

¿Cuáles son las causas más comunes?

Cuando el log nombra el síntoma, la causa casi siempre es una de estas cinco:

El log dice Causa probable Arreglo
status=203/EXEC Ruta de ExecStart= incorrecta o intérprete que falta Ruta absoluta, chmod +x, revisa el shebang
status=203/EXEC en un script El script tiene fines de línea CRLF o un shebang malo dos2unix script.sh, arregla la primera línea
status=1/FAILURE, sin log de la app Falta el directorio de trabajo o una variable de entorno Define WorkingDirectory=, añade Environment=
Permission denied El usuario no puede leer archivos o bindear el puerto Arregla los dueños; los puertos por debajo de 1024 requieren AmbientCapabilities=CAP_NET_BIND_SERVICE o root
Unit is masked Alguien corrió systemctl mask systemctl unmask myapp.service
Editar el unit file "no hace nada" El daemon no se recargó systemctl daemon-reload

La familia 203/EXEC merece mención aparte porque es la que se come las tardes. Systemd no usa tu shell para lanzar ExecStart=. Eso significa:

# Mal — sin shell, ~ nunca se expande, no hay búsqueda en PATH
ExecStart=~/app/run.sh

# Bien
ExecStart=/opt/app/run.sh

Y el script en sí debe ser ejecutable y empezar con un shebang real (#!/bin/bash o #!/usr/bin/env bash). Un script que corre bien en tu terminal pero falla con 203 bajo systemd es casi siempre una de estas: no es ejecutable, tiene fines de línea CRLF, el shebang apunta a ningún lado o usa una ruta relativa.

¿Por qué arranca a mano pero no al boot?

El bug de ordenamiento clásico. Si el log muestra fallos justo después del boot pero la unidad arranca bien cuando haces systemctl start a mano, tu servicio está perdiendo una carrera — va por la red, un disco montado o una base de datos antes de que eso exista.

El arreglo es declarar dependencias en vez de confiar en la suerte:

[Unit]
After=network-online.target postgresql.service
Wants=network-online.target

[Service]
ExecStartPre=/usr/bin/test -f /opt/app/config.toml

After= ordena el arranque; Wants= hace que systemd de verdad levante la dependencia. network-online.target solo funciona si el servicio de network-wait está habilitado en tu distro, así que revisa systemctl is-enabled NetworkManager-wait-online.service (o el equivalente de systemd-networkd). El guardia de ExecStartPre= es una forma barata y honesta de fallar fuerte con un mensaje legible en lugar de un stack trace.

Una segunda variante: el servicio arranca en el boot pero muere de inmediato. Busca cosas que tu entorno interactivo tenía y el boot no — diferencias de PATH, un virtualenv, un HOME. Define lo que necesitas de forma explícita:

[Service]
Environment="PATH=/opt/app/venv/bin:/usr/bin"
User=appuser
WorkingDirectory=/opt/app

¿Por qué mi servicio no loggea al journal?

Si journalctl -u no muestra nada, revisa tres cosas en orden:

  1. StandardOutput= y StandardError= en la unidad — deben ser journal (el valor por defecto) o journal+console. Alguien pudo ponerlos en null.
  2. La app escribe a un archivo en vez de stdout. Systemd solo captura stdout/stderr; quien escribe a un archivo de log se salta el journal por completo. O apuntas la app a stdout o lees el archivo.
  3. Los límites de almacenamiento tiraron líneas viejas: journalctl --disk-usage, y SystemMaxUse= en /etc/systemd/journald.conf si el journal está ahogado.

Para depurar el arranque en sí, nada supera meter una shell en el contexto de boot:

[Service]
ExecStart=/bin/bash -c 'exec /opt/app/bin/server 2>&1'

O, para misterios genuinos, corre el comando exacto de ExecStart= como el usuario de la unidad en una shell — la mayoría de las diferencias de entorno aparecen en los primeros diez segundos.

¿Cómo hago que se reinicie solo tras un crash?

La política por defecto es Restart=no: un servicio crasheado se queda muerto, y te enteras por un usuario. Arréglalo por servicio:

[Service]
Restart=on-failure
RestartSec=5
Valor Cuándo reinicia
no (por defecto) Nunca
on-failure Exit distinto de cero, señal, timeout
always Cualquier exit, incluso limpio
on-watchdog Solo timeout del watchdog

Combina Restart=on-failure con un guardia de tasa de arranque para que un loop de crashes no martillee la máquina: StartLimitIntervalSec= y StartLimitBurst= en la sección [Unit]. Cinco fallos en sesenta segundos deberían avisar a un humano, no girar un CPU.

Si lo estás montando como job programado en vez de daemon, pesa primero cron vs systemd timer — los timers te dan logging al journal y orden de dependencias gratis, que es justo lo que este artículo anda buscando.

El checklist de 60 segundos

  1. systemctl status <unit> — lee el estado y las últimas líneas.
  2. journalctl -u <unit> -n 50 --no-pager — encuentra el error real.
  3. ¿203/EXEC? Arregla ruta, shebang, permisos. ¿Permission denied? Arregla usuario y dueños de archivos.
  4. ¿Falla solo en el boot? Añade After=/Wants=network-online.target y un guardia ExecStartPre=.
  5. ¿Cambias un unit file? systemctl daemon-reload && systemctl restart <unit>.
  6. Añade Restart=on-failure para que el próximo crash se anuncie en vez de esconderse.

La mayoría de los momentos de "systemd es complicado" se reducen a el error estuvo en el journal todo el tiempo. Léelo antes de editar el unit file, no después.