El servicio systemd no arranca — las cinco causas, y el log que las nombra
Casi nunca es un misterio: systemctl status, las últimas 50 líneas del journal — y una de las cinco causas queda nombrada en negro sobre blanco. Los arreglos, después, son emparejar patrones.
■ espacio · carril prosa
systemctl status + journalctl -u <unit> -n 50 --no-pager — una de las cinco causas queda nombrada en el log.
203/EXEC = ruta, shebang o bit de ejecución — systemd lanza ExecStart= sin shell.
¿Falla solo en el arranque? Una carrera de orden — añade After=/Wants= y una guardia ExecStartPre=.
LOS DOS COMANDOS
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 cuenta la historia completa: stdout, stderr y las quejas propias de systemd sobre la unidad. ¿Falló antes y quieres el registro de esa corrida? Añade -b para el arranque actual, o --since today. Una última pareja que conviene conocer:
systemctl list-units --failed # every red unit on the box
systemctl reset-failed myapp.service # clear the failed state after the fix
verify: systemctl list-units --failed → 0 unidades cargadas listadas
LAS CINCO CAUSAS
Cuando el log nombra el síntoma, la causa casi siempre es una de estas — revisa primero la más probable:
| El log dice | Causa probable | Arreglo |
|---|---|---|
status=203/EXEC | Mala ExecStart=ruta o intérprete ausente | Ruta absoluta, chmod +x, revisa el shebang |
status=203/EXEC en un script | Fines de línea CRLF o shebang malo | dos2unix script.sh, arregla la primera línea |
status=1/FAILURE, sin log de la app | Falta directorio de trabajo o variable de entorno | Configura WorkingDirectory=, añade Environment= |
Permission denied | El usuario no puede leer archivos ni selar el puerto | Arregla la propiedad; los puertos bajo 1024 exigen AmbientCapabilities=CAP_NET_BIND_SERVICE o root |
Unit is masked | Alguien ejecutó systemctl mask | systemctl unmask myapp.service |
| Editar el archivo de unidad «no hace nada» | Daemon sin recargar | systemctl daemon-reload |
LA FAMILIA 203/EXEC
La que devora tardes. Systemd no usa tu shell para lanzar ExecStart= — sin expansión de ~, sin PATH lookup, sin cd. Mira cómo las líneas equivocadas se vuelven correctas:
Un script que va de perlas en tu terminal pero falla con 203 bajo systemd es casi siempre una de esas cuatro: no ejecutable, fines CRLF, shebang que no apunta a nada, o ruta relativa.
VA A MANO, MUERE EN EL ARRANQUE
El fallo en el arranque es orden, no sintaxis. La carrera clásica: tu servicio alcanza la red, un disco montado o una base de datos antes de que eso exista. Declara dependencias en lugar de esperar:
[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 suba realmente la dependencia. network-online.target solo funciona si el servicio network-wait está habilitado en tu distro — revisa systemctl is-enabled NetworkManager-wait-online.service (o el equivalente de systemd-networkd). La guardia ExecStartPre= falla ruidosamente con un mensaje legible en lugar de una traza.
verify: systemctl is-enabled NetworkManager-wait-online.service → enabled
La segunda variante: arranca al inicio y muere inmediatamente. Busca lo que tenía tu entorno interactivo y no tiene el arranque — PATH distintas, un virtualenv, un HOME. Systemd arranca las unidades con un entorno despojado; todo lo que tu shell aporta sin decirlo hay que deletrearlo:
[Service]
Environment="PATH=/opt/app/venv/bin:/usr/bin"
User=appuser
WorkingDirectory=/opt/app
Para misterios genuinos, ejecuta el comando exacto ExecStart= como el usuario de la unidad, en un shell — la mayoría de las diferencias de entorno afloran en los primeros diez segundos.
¿NADA EN EL JOURNAL?
StandardOutput=yStandardError=en la unidad — deben estar enjournal(por defecto) ojournal+console. Alguien pudo ponerlos ennull.- La app escribe en un archivo en lugar de stdout. Systemd solo captura stdout/stderr; quien escribe a un archivo de log salta el journal por completo. Apunta la app a stdout o lee el archivo.
- Límites de almacenamiento descartaron líneas viejas:
journalctl --disk-usage, ySystemMaxUse=en/etc/systemd/journald.confsi el journal se está ahogando.
REINICIAR TRAS UN CRASH
La política por defecto es Restart=no: un servicio crasheado queda muerto, y te enteras por un usuario. Arréglalo servicio a servicio:
[Service]
Restart=on-failure
RestartSec=5
verify: systemctl show myapp.service --property=Restart → Restart=on-failure
| Opción | Reinicia cuando |
|---|---|
no (por defecto) | Nunca |
on-failure | Código de salida no-cero, señal, timeout |
always | Cualquier salida, incluso limpia |
on-watchdog | Solo timeout del watchdog |
Empareja Restart=on-failure con una guardia de tasa de arranque para que un bucle de crash no martillee la máquina: StartLimitIntervalSec= y StartLimitBurst= en la sección [Unit]. Cinco fallos en sesenta segundos deben paginar a un humano, no hacer girar una CPU. Y si lo estás cableando como tarea programada en lugar de un daemon, los timers te dan logging en el journal y orden de dependencias gratis — pesa primero cron contra un timer.
LA LISTA DE 60 SEGUNDOS
systemctl status <unit>— lee el estado y las últimas líneas.journalctl -u <unit> -n 50 --no-pager— encuentra el error real.203/EXEC? Arregla ruta, shebang, permisos.Permission denied? Arregla el usuario y la propiedad de los archivos.- ¿Falla solo en el arranque? Añade
After=/Wants=network-online.targety una guardiaExecStartPre=. - ¿Cambiaste un archivo de unidad?
systemctl daemon-reload && systemctl restart <unit>. - Añade
Restart=on-failurepara que el próximo crash se anuncie en lugar de esconderse.
La mayoría de los momentos «systemd es complicado» se reducen a el error estuvo en el journal desde el principio. Léelo antes de editar el archivo de unidad, no después.
fin de las cinco causas · nada descargado · todo local