Automatizar el inicio de servicios después de reiniciar un servidor parece sencillo. Una alternativa habitual es utilizar cron con la directiva @reboot:
@reboot /bin/bash /usr/local/bin/arranque_servicios.sh
Sin embargo, podemos encontrarnos con una situación desconcertante: el script funciona perfectamente al ejecutarlo manualmente como root, pero falla cuando se ejecuta automáticamente después de reiniciar el servidor.
Recientemente nos encontramos con este escenario en un servidor que ejecuta una plataforma DSpace, donde era necesario iniciar automáticamente servicios como Solr, Tomcat, Apache y Nginx.
El script de arranque podía ejecutarse manualmente sin inconvenientes:
sh /usr/local/bin/arranque_servicios.sh
Solr encontraba Java correctamente:
Java 17 detected.
Started Solr server on port 8983.
Tomcat también iniciaba normalmente:
Using JAVA_HOME: /opt/java
Tomcat started.
Sin embargo, después de reiniciar el servidor, la plataforma respondía con un error HTTP 500.
El @reboot existía y aparentemente estaba configurado correctamente, por lo que el primer paso fue dejar de ejecutar el proceso "a ciegas" y registrar su salida.
@rebootModificamos la entrada de cron para guardar tanto la salida estándar como los errores:
@reboot /bin/bash /usr/local/bin/arranque_servicios.sh >> /var/log/arranque_servicios.log 2>&1
Después del siguiente arranque pudimos revisar:
cat /var/log/arranque_servicios.log
Y apareció inmediatamente la causa:
Java not found, or an error was encountered when running java.
java: orden no encontrada
JAVA_HOME: N/A
Active Path:
/usr/bin:/bin:/usr/sbin:/sbin
Tomcat entregaba un mensaje igualmente revelador:
Neither the JAVA_HOME nor the JRE_HOME environment variable is defined.
At least one of these environment variable is needed to run this program.
cron sí estaba ejecutando el script. El problema era el entorno en el que lo ejecutaba.
Cuando iniciamos sesión como usuario o como root, Linux carga diferentes archivos de configuración del entorno, dependiendo de la distribución y del tipo de sesión.
Estos pueden establecer variables como:
JAVA_HOME
JRE_HOME
PATH
Por eso, en una sesión interactiva podemos ejecutar:
java -version
y obtener correctamente Java 17.
cron, en cambio, ejecuta los procesos con un entorno mucho más reducido. En nuestro caso su PATH era:
/usr/bin:/bin:/usr/sbin:/sbin
Java estaba instalado en:
/opt/java/bin/java
Por lo tanto, cron simplemente no podía encontrarlo.
Esta diferencia explica por qué probar un script manualmente como root no garantiza que vaya a funcionar mediante cron.
La solución fue hacer que el script fuera autosuficiente y no dependiera de las variables cargadas durante una sesión interactiva.
Al comienzo de /usr/local/bin/arranque_servicios.sh agregamos:
#!/bin/bash
export JAVA_HOME=/opt/java
export JRE_HOME=/opt/java
export PATH=/opt/java/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
El script completo quedó con una estructura similar a:
#!/bin/bash
export JAVA_HOME=/opt/java
export JRE_HOME=/opt/java
export PATH=/opt/java/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
sleep 5
# Detener Nginx
service nginx stop
sleep 5
# Iniciar Solr
/opt/solr/bin/solr start --force
sleep 10
# Reiniciar Apache
service httpd stop
sleep 5
service httpd start
sleep 5
# Iniciar Tomcat
/opt/tomcat/bin/startup.sh
sleep 10
# Iniciar Nginx
systemctl start nginx
Después del cambio, el registro mostró:
Java 17 detected.
Started Solr server on port 8983.
seguido de:
Tomcat started.
Finalmente, Nginx volvió a levantar el acceso público a la plataforma.
Que el script termine sin errores no es suficiente. Es recomendable comprobar cada componente.
Para Nginx:
systemctl status nginx --no-pager
Para Apache:
systemctl status httpd --no-pager
Para Tomcat:
ps aux | grep '[o]rg.apache.catalina.startup.Bootstrap'
Para Solr:
ps -fea | grep '[s]olr'
Y podemos revisar directamente los puertos:
ss -lntp
En nuestro caso, después de la corrección, Nginx y Apache aparecieron como:
Active: active (running)
Tomcat estaba ejecutándose mediante Java y Solr se encontraba activo en el puerto 8983.
Una pequeña modificación como esta:
@reboot /bin/bash /usr/local/bin/arranque_servicios.sh >> /var/log/arranque_servicios.log 2>&1
puede ahorrar bastante tiempo de diagnóstico.
Sin ella, solamente observamos el síntoma:
"Después de reiniciar el servidor, la aplicación no levantó."
Con el registro obtenemos inmediatamente información como:
JAVA_HOME: N/A
y:
java: orden no encontrada
La diferencia entre ambos escenarios puede significar pasar horas buscando un problema o encontrarlo en pocos minutos.
Aunque @reboot puede ser suficiente para automatizaciones sencillas, cuando se administran servicios de infraestructura resulta conveniente considerar systemd.
systemd permite definir explícitamente dependencias y orden de inicio, controlar reinicios automáticos y consultar los registros mediante journalctl.
Por ejemplo, podemos establecer que determinado servicio se ejecute solamente después de que la red esté disponible:
[Unit]
Description=Arranque de servicios de la plataforma
After=network-online.target
Wants=network-online.target
Para entornos de producción con múltiples componentes relacionados, esta aproximación suele ser más robusta que depender exclusivamente de tiempos de espera mediante sleep.
Cuando un script funciona manualmente pero falla mediante cron @reboot, no debemos asumir inmediatamente que cron no lo está ejecutando.
Primero debemos preguntarnos:
¿Se está ejecutando con el mismo entorno?
Variables como JAVA_HOME, JRE_HOME y PATH pueden existir en nuestra sesión interactiva y estar completamente ausentes durante el arranque automático.
En este caso, tres acciones permitieron resolver y diagnosticar correctamente el problema:
@reboot en un archivo de log.Una solución sencilla, pero también un buen recordatorio para la administración de servidores Linux:
Un script de producción debería declarar explícitamente el entorno del que depende, en lugar de asumir que será el mismo que el de nuestra sesión de terminal.