Tutoriales de VPS
Qué hacer cuando un servicio systemd no arranca en un VPS (2026): systemctl status, journalctl -u y diagnóstico del archivo Unit
Cuando un servicio en un VPS falla al ejecutar systemctl start, muestra Active como failed o se reinicia automáticamente una y otra vez, las causas comunes incluyen errores de sintaxis en el archivo Unit, rutas incorrectas en ExecStart, WorkingDirectory inexistente, fallos al leer EnvironmentFile, permisos insuficientes del usuario de ejecución, puerto ocupado, servicios de dependencia no iniciados o StartLimitHit. Este artículo diagnostica capa por capa usando systemctl status, journalctl -u, systemctl cat, daemon-reload, permisos y bucles de reinicio.

Cuando un servicio falla al arrancar en un VPS, mucha gente solo repite:
sudo systemctl restart app
Si el problema es que la configuración no se cargó, una ruta está mal escrita, los permisos son insuficientes o el puerto está ocupado, reiniciar cien veces no sirve de nada. Peor aún, si el Unit está configurado con Restart=always, el servicio puede entrar en un bucle de reinicio, inundar los logs y ralentizar la CPU y el disco.
Este artículo trata específicamente de cómo investigar un servicio systemd que falla al arrancar. No es un diagnóstico exclusivo de un software como Nginx, MySQL o Redis, sino el problema básico que enfrenta todo servicio autoalojado: cómo leer systemctl status, qué líneas mirar en journalctl -u, cómo confirmar el archivo Unit, cuándo se necesita daemon-reload y cómo manejar Start request repeated too quickly.
Primero mira el estado del servicio:
sudo systemctl status app --no-pager
Reemplaza app con el nombre de tu servicio, por ejemplo:
sudo systemctl status nginx --no-pager
sudo systemctl status docker --no-pager
sudo systemctl status myapp --no-pager
Fíjate en estos campos:
Loaded:si el archivo Unit existe y si está enabled;Active:si esactive,failed,activatingoauto-restart;Main PID:si el proceso principal sigue vivo;ExecStart=si el comando posterior realmente se ejecutó;code=exited, status=...cuál es el código de salida;- si las últimas líneas del log contienen un error claro.
Si solo ves un fragmento pequeño de log, no es suficiente. A continuación, mira el journal completo.
El comando más habitual:
sudo journalctl -u app -n 120 --no-pager
Para ver la última hora:
sudo journalctl -u app --since "1 hour ago" --no-pager
Para seguir en tiempo real:
sudo journalctl -u app -f
Si acaba de fallar al arrancar, puedes reproducirlo y ver el log de inmediato así:
sudo systemctl restart app
sudo journalctl -u app -n 120 --no-pager
No mires solo la última línea. La causa real suele estar antes, por ejemplo:
No such file or directory
Permission denied
Address already in use
Failed at step USER
Failed to load environment files
Working directory ... does not exist
Start request repeated too quickly
Si aún no estás familiarizado con journalctl, puedes leer primero este artículo: Cómo leer los logs de un VPS (journalctl, systemctl, Nginx, logs de aplicación).
Mucha gente edita el archivo equivocado.
Primero mira el contenido del Unit que systemd ve realmente:
sudo systemctl cat app
Muestra el archivo Unit principal y las configuraciones de override drop-in, por ejemplo:
# /etc/systemd/system/app.service
[Service]
WorkingDirectory=/var/www/app
ExecStart=/usr/bin/node server.js
# /etc/systemd/system/app.service.d/override.conf
[Service]
Environment=NODE_ENV=production
Si claramente cambiaste /etc/systemd/system/app.service pero systemctl cat app no muestra cambios, normalmente es porque olvidaste:
sudo systemctl daemon-reload
Orden recomendado tras modificar un archivo Unit:
sudo systemctl daemon-reload
sudo systemctl restart app
sudo systemctl status app --no-pager
Cambiar solo el archivo de configuración de la aplicación no necesariamente requiere daemon-reload; cambiar .service, .timer o un override drop-in normalmente sí.
En el Unit, ExecStart es mejor escribirlo como ruta absoluta:
[Service]
ExecStart=/usr/bin/node /var/www/app/server.js
No dependas del PATH, nvm, pyenv o conda de tu shell de inicio de sesión. Cuando systemd arranca un servicio, el entorno es distinto del que tienes tras iniciar sesión por SSH.
Primero comprueba la ruta del comando:
which node
which python3
which php
which gunicorn
Si instalaste Node con nvm, which node puede estar en el directorio home del usuario:
/home/deploy/.nvm/versions/node/v20.11.1/bin/node
Este tipo de ruta también funciona en systemd, pero debes confirmar que el usuario de ejecución tiene permisos y no asumir que .bashrc se cargará. Una forma más robusta es fijar el entorno de ejecución a una ruta explícita o escribir un script wrapper.
Muchas aplicaciones arrancan bien manualmente pero fallan bajo systemd porque el directorio de trabajo es distinto.
Configuración habitual:
[Service]
WorkingDirectory=/var/www/app
ExecStart=/usr/bin/npm start
Comprueba el directorio:
ls -ld /var/www/app
Si en el log aparece:
CHDIR spawning ... No such file or directory
Working directory ... does not exist
significa que la ruta de WorkingDirectory es incorrecta o que el directorio fue eliminado.
Si el directorio existe pero el usuario del servicio no puede entrar, también fallará. Por ejemplo, si el servicio usa User=www-data, prueba con ese usuario:
sudo -u www-data test -x /var/www/app
sudo -u www-data ls /var/www/app
Configuración habitual del Unit:
[Service]
User=deploy
Group=deploy
Si en el log aparece:
Failed at step USER spawning ...: No such process
significa que el usuario no existe o que systemd no puede cambiar a ese usuario.
Comprueba el usuario:
id deploy
Si es un problema de permisos de archivos, el log puede ser:
Permission denied
Ejecuta manualmente el comando de arranque con el usuario objetivo:
sudo -u deploy /usr/bin/node /var/www/app/server.js
Esto es más informativo que ejecutarlo manualmente como root. Que root pueda ejecutarlo no significa que deploy, www-data, postgres o redis puedan.
Muchos servicios se configuran así:
[Service]
EnvironmentFile=/etc/app/app.env
ExecStart=/usr/bin/node /var/www/app/server.js
Si el archivo no existe, el log puede mostrar:
Failed to load environment files: No such file or directory
Comprueba:
sudo ls -l /etc/app/app.env
sudo systemctl cat app
Si este archivo de entorno no es obligatorio, puedes añadir - para que no dé un error grave cuando no exista:
EnvironmentFile=-/etc/app/app.env
Pero no añadas - sin más para "que arranque". Si la aplicación depende de la dirección de la base de datos, secretos o configuración de puerto, un archivo de entorno ausente solo hará que el error sea más oculto.
También ten en cuenta el formato del archivo de entorno: no es un script shell completo. Formato habitual:
NODE_ENV=production
PORT=3000
DATABASE_URL=postgres://user:[email protected]:5432/app
No escribas lógica shell compleja dentro.
Si en el log aparece:
Address already in use
EADDRINUSE
bind: address already in use
primero comprueba quién ocupa el puerto:
sudo ss -lntup
Para ver solo un puerto concreto:
sudo ss -lntup | grep ':3000'
Causas comunes:
- Un proceso antiguo no terminó;
- El mismo servicio se inició dos veces;
- Un contenedor Docker ocupa el puerto;
- Nginx / Caddy / Apache compite con la aplicación por el 80/443;
- Varios servicios en la configuración usan el mismo puerto.
No hagas kill -9 directamente. Primero confirma el origen del proceso:
ps -fp PID
systemctl status PID对应服务
Si un problema de puerto provoca un 502 en el sitio web, puedes leer: Qué hacer ante 502 / 504 Bad Gateway en un VPS.
Habitual en los Units:
[Unit]
After=network.target postgresql.service
Requires=postgresql.service
Esto solo garantiza el orden y las relaciones de dependencia; no significa que PostgreSQL ya pueda aceptar conexiones, ni que una base de datos remota en la red sea accesible.
Si el log de la aplicación muestra:
connection refused
connection timed out
could not connect to server
comprueba también los servicios de dependencia:
sudo systemctl status postgresql --no-pager
sudo ss -lntup | grep ':5432'
Si es Docker, Redis, MySQL o una API remota, también debes confirmar la accesibilidad por separado. No atribuyas todos los problemas de dependencia a systemd.
Si ves:
Start request repeated too quickly
Failed with result 'start-limit-hit'
significa que el servicio falló demasiadas veces en poco tiempo y systemd ha pausado su arranque.
Primero mira la causa del fallo:
sudo journalctl -u app -n 200 --no-pager
Tras corregir la causa raíz, restablece el estado de fallo:
sudo systemctl reset-failed app
sudo systemctl start app
Si el Unit contiene:
[Service]
Restart=always
RestartSec=1
el servicio se reiniciará con mucha frecuencia. En producción puedes aumentarlo adecuadamente:
[Service]
Restart=on-failure
RestartSec=5s
No uses una política de reinicio más agresiva para ocultar fallos de arranque. Solo hará que los logs sean más caóticos.
Tipos comunes:
Type=simple
Type=exec
Type=forking
Type=oneshot
Para la mayoría de aplicaciones que se ejecutan en primer plano, simple o exec es suficiente. No escribas un proceso en primer plano como forking.
Si el servicio sale inmediatamente tras arrancar pero es una tarea de una sola ejecución, deberías considerar:
[Service]
Type=oneshot
RemainAfterExit=yes
Si es un servicio de larga duración, que el proceso salga significa que la aplicación terminó; mira los logs de la aplicación en lugar de disfrazarlo como éxito.
Se recomienda seguir este orden cada vez que modifiques un Unit de systemd:
sudo systemd-analyze verify /etc/systemd/system/app.service
sudo systemctl daemon-reload
sudo systemctl restart app
sudo systemctl status app --no-pager
sudo journalctl -u app -n 80 --no-pager
systemd-analyze verify puede detectar por adelantado algunos problemas de sintaxis y configuración del Unit. No garantiza que la aplicación arranque, pero evita errores básicos del Unit.
Si el servicio en línea es importante, haz una copia de seguridad antes de cambiar:
sudo cp /etc/systemd/system/app.service /etc/systemd/system/app.service.bak.$(date +%F-%H%M)
Para revertir:
sudo cp /etc/systemd/system/app.service.bak.时间戳 /etc/systemd/system/app.service
sudo systemctl daemon-reload
sudo systemctl restart app
Cuando systemctl start o restart fallen, comprueba en este orden:
systemctl status app --no-pagerpara ver Active, el código de salida y los últimos logs.journalctl -u app -n 120 --no-pagerpara ver la causa completa del fallo.systemctl cat apppara confirmar el Unit y el override realmente cargados.- Tras modificar el Unit, ejecuta
systemctl daemon-reload. - Comprueba si
ExecStartes una ruta absoluta y si el comando existe. - Comprueba si
WorkingDirectoryexiste y si el usuario del servicio puede acceder. - Comprueba si
User/Groupexisten y si los permisos son correctos. - Comprueba si
EnvironmentFileexiste y si el formato es correcto. - Usa
ss -lntuppara comprobar si el puerto está ocupado. - Comprueba si los servicios de dependencia están iniciados y realmente son conectables.
- Cuando encuentres
start-limit-hit, corrige primero la causa raíz y luegosystemctl reset-failed app. - Haz copia de seguridad del Unit antes de modificar y usa
systemd-analyze verifypara una comprobación básica después.
Cuando un servicio systemd falla al arrancar, no confíes en reinicios repetidos y la suerte. Primero usa systemctl status para determinar el estado, luego usa journalctl -u para encontrar el primer error real y después mira systemctl cat para confirmar si el Unit se carga como esperas.
La mayoría de los problemas acaban en unas pocas categorías: ruta incorrecta, permisos insuficientes, archivo de entorno ausente, conflicto de puerto, dependencia no iniciada, Unit modificado pero sin daemon-reload, o servicio que entró en StartLimitHit. Diagnosticar en orden es mucho más rápido que cambiar la configuración a ciegas, y no empeora la situación cuanto más cambias.