Spring Boot empaqueta la aplicación y su servidor web (Tomcat por defecto) en un único JAR ejecutable, así que desplegarla consiste en instalar Java, copiar el JAR y mantenerlo en marcha. En este tutorial compilarás una aplicación Spring Boot en Ubuntu 24.04 con Java 21, la ejecutarás como servicio de systemd con un usuario sin privilegios y la publicarás detrás de Nginx con un certificado de Let's Encrypt.
Requisitos previos
Para seguir esta guía necesitas:
- Un servidor con Ubuntu 24.04 LTS, por ejemplo un VPS de CubePath, con al menos 2 GB de RAM.
- Un usuario no root con privilegios
sudo. - Un dominio con un registro DNS
Aapuntando a la IP del servidor. En los ejemplos se usayour_domain. - Una aplicación Spring Boot 3 o 4 con Maven o Gradle, o las ganas de probar con un proyecto nuevo generado en Spring Initializr.
La aplicación de ejemplo se llama myapp. Sustituye el nombre en todos los comandos y archivos.
Paso 1: Instalar Java 21
Spring Boot 3 y 4 requieren Java 17 o superior. Ubuntu 24.04 incluye OpenJDK 21, la versión LTS recomendada. Instala el JDK, que incluye el compilador necesario para construir el JAR en el servidor, junto con Nginx y unzip:
sudo apt update
sudo apt install openjdk-21-jdk-headless nginx unzip
Comprueba la versión:
java -version
openjdk version "21.0.x" ...
OpenJDK Runtime Environment (build 21.0.x+...-Ubuntu-...)
OpenJDK 64-Bit Server VM (build 21.0.x+..., mixed mode, sharing)
Notasi compilas el JAR en tu equipo o en un pipeline de CI y solo lo copias al servidor, basta con el paquete
openjdk-21-jre-headless.
Abre en el cortafuegos SSH, HTTP y HTTPS. El puerto 8080 de la aplicación no se abre: solo Nginx accederá a él de forma local.
sudo ufw allow OpenSSH
sudo ufw allow 'Nginx Full'
sudo ufw enable
Paso 2: Compilar la aplicación
Si tienes la aplicación en un repositorio, clónala y compílala con el wrapper que incluye el proyecto (mvnw o gradlew), que descarga la versión correcta de Maven o Gradle:
git clone https://github.com/your_account/myapp.git ~/myapp
cd ~/myapp
./mvnw -DskipTests package
Con Gradle, el equivalente es ./gradlew bootJar y el JAR queda en build/libs/.
Si solo quieres probar el despliegue, genera un proyecto con Spring Initializr que incluya Spring Web y Actuator, que aporta el endpoint de salud /actuator/health:
curl -fsSL https://start.spring.io/starter.zip -d type=maven-project -d javaVersion=21 -d dependencies=web,actuator -d artifactId=myapp -d name=myapp -o myapp.zip
unzip myapp.zip -d ~/myapp
cd ~/myapp
./mvnw -DskipTests package
La primera compilación descarga las dependencias y tarda un par de minutos. Al terminar, comprueba que existe el JAR:
ls target/*.jar
target/myapp-0.0.1-SNAPSHOT.jar
Paso 3: Crear el usuario y el directorio de la aplicación
Ejecutar la aplicación con un usuario de sistema sin shell limita el daño si alguien la compromete. Crea el usuario myapp y el directorio /opt/myapp:
sudo useradd --system --home-dir /opt/myapp --shell /usr/sbin/nologin myapp
sudo mkdir -p /opt/myapp/config
Copia el JAR con un nombre fijo, para que la unidad de systemd no tenga que cambiar con cada versión:
sudo cp ~/myapp/target/myapp-0.0.1-SNAPSHOT.jar /opt/myapp/myapp.jar
Paso 4: Configurar la aplicación para producción
Spring Boot carga automáticamente config/application.properties desde el directorio de trabajo, y sus valores tienen prioridad sobre los empaquetados en el JAR. Así puedes mantener la configuración de producción fuera del código:
sudo nano /opt/myapp/config/application.properties
server.address=127.0.0.1
server.port=8080
server.forward-headers-strategy=native
management.endpoints.web.exposure.include=health
server.address=127.0.0.1hace que Tomcat solo acepte conexiones locales.server.forward-headers-strategy=nativehace que la aplicación use las cabecerasX-Forwarded-*de Nginx para conocer la IP del cliente y el esquemahttps.management.endpoints.web.exposure.include=healthexpone solo el endpoint de salud de Actuator.
Si la aplicación usa una base de datos, añade aquí también spring.datasource.url, spring.datasource.username y spring.datasource.password.
Asigna los archivos al usuario de la aplicación y protege la configuración, que puede contener contraseñas:
sudo chown -R myapp:myapp /opt/myapp
sudo chmod 640 /opt/myapp/config/application.properties
Paso 5: Crear el servicio de systemd
Crea la unidad del servicio:
sudo nano /etc/systemd/system/myapp.service
[Unit]
Description=myapp Spring Boot application
After=network-online.target
Wants=network-online.target
[Service]
User=myapp
Group=myapp
WorkingDirectory=/opt/myapp
ExecStart=/usr/bin/java -Xms256m -Xmx512m -jar /opt/myapp/myapp.jar
SuccessExitStatus=143
Restart=on-failure
RestartSec=10
[Install]
WantedBy=multi-user.target
-Xms256m -Xmx512mfijan el tamaño mínimo y máximo del heap. Ajusta-Xmxa la memoria del servidor, dejando margen para el sistema y para la memoria de la JVM que no es heap.SuccessExitStatus=143indica a systemd que la salida de Java al recibirSIGTERMes una parada normal y no un fallo.
Activa e inicia el servicio:
sudo systemctl daemon-reload
sudo systemctl enable --now myapp
El arranque tarda unos segundos. Sigue el log hasta ver que Tomcat escucha:
sudo journalctl -u myapp -f
... TomcatWebServer : Tomcat started on port 8080 (http) with context path '/'
... MyappApplication : Started MyappApplication in 2.9 seconds (process running for 3.4)
Pulsa Ctrl+C para salir y comprueba el endpoint de salud:
curl http://127.0.0.1:8080/actuator/health
{"status":"UP"}
Paso 6: Configurar Nginx como proxy inverso
Crea un bloque de servidor para el dominio:
sudo nano /etc/nginx/sites-available/myapp
El archivo proxy_params de Ubuntu añade las cabeceras Host, X-Real-IP, X-Forwarded-For y X-Forwarded-Proto que la aplicación espera:
server {
listen 80;
listen [::]:80;
server_name your_domain www.your_domain;
client_max_body_size 20M;
location / {
include proxy_params;
proxy_pass http://127.0.0.1:8080;
}
}
Activa el sitio, desactiva el sitio por defecto y comprueba la sintaxis:
sudo ln -s /etc/nginx/sites-available/myapp /etc/nginx/sites-enabled/
sudo rm /etc/nginx/sites-enabled/default
sudo nginx -t
sudo systemctl reload nginx
Comprueba que Nginx llega a la aplicación:
curl http://your_domain/actuator/health
{"status":"UP"}
Paso 7: Activar HTTPS con Let's Encrypt
Instala Certbot con su plugin para Nginx y solicita el certificado. Certbot añade la configuración TLS y la redirección de HTTP a HTTPS:
sudo apt install certbot python3-certbot-nginx
sudo certbot --nginx -d your_domain -d www.your_domain
Comprueba que la renovación automática funciona:
sudo certbot renew --dry-run
Abre https://your_domain/actuator/health en el navegador y verás la respuesta {"status":"UP"} servida por HTTPS.
Actualizar la aplicación
Para desplegar una nueva versión, compílala, sustituye el JAR y reinicia el servicio:
cd ~/myapp
git pull
./mvnw -DskipTests package
sudo install -o myapp -g myapp -m 644 target/myapp-0.0.1-SNAPSHOT.jar /opt/myapp/myapp.jar
sudo systemctl restart myapp
Desde Spring Boot 3.4 el apagado ordenado (graceful shutdown) está activo por defecto, así que las peticiones en curso terminan antes de que el proceso se detenga.
Solución de problemas
- 502 Bad Gateway justo después de reiniciar: la aplicación aún está arrancando. Espera a ver
Started ...enjournalctl -u myapp. - El servicio se reinicia en bucle: revisa
sudo journalctl -u myapp -n 100. Las causas típicas son un error enapplication.properties, una base de datos inaccesible ojava.lang.OutOfMemoryError, que se corrige subiendo-Xmxsi el servidor tiene memoria libre. Web server failed to start. Port 8080 was already in use: otro proceso usa el puerto. Localízalo consudo ss -ltnp | grep 8080o cambiaserver.porty elproxy_passde Nginx.- Las redirecciones de la aplicación apuntan a
http://: faltaserver.forward-headers-strategy=nativeo Nginx no está enviandoX-Forwarded-Proto.
Conclusión
Tu aplicación Spring Boot se ejecuta como servicio de systemd con un usuario sin privilegios, escucha solo en local y Nginx la publica por HTTPS. Como siguientes pasos puedes:
- Añadir una base de datos PostgreSQL o MySQL y configurar
spring.datasource.*en el archivo de producción. - Exponer métricas de Actuator para Prometheus y proteger esos endpoints.
- Construir el JAR en un pipeline de CI y copiar solo el artefacto al servidor.
