Tomcat y Spring Boot con Let's Encrypt

Antes había que convertir el PEM en un keystore y repetirlo en cada renovación. Las versiones actuales de Tomcat y Spring Boot leen directamente los archivos de Let's Encrypt, y Spring Boot incluso los recarga solo.

¿Qué vía encaja?

Obtener el certificado

Let's Encrypt valida por HTTP-01 en el puerto 80 o por DNS-01. Si no hay nada en el puerto 80, basta el modo standalone de certbot; si no, webroot o DNS. El asistente ACME te da los comandos. Después los archivos están en /etc/letsencrypt/live/example.com/:

La trampa de los permisos

Los directorios live y archive solo los puede leer root. Tomcat y Spring Boot se ejecutan con su propio usuario y reciben «Permission denied». En lugar de relajar permisos, un deploy hook copia los archivos tras cada renovación a un sitio que la aplicación pueda leer:

#!/bin/sh
# /etc/letsencrypt/renewal-hooks/deploy/tomcat.sh  (hacer ejecutable)
for f in cert.pem chain.pem fullchain.pem privkey.pem; do
  install -o tomcat -g tomcat -m 0600 "$RENEWED_LINEAGE/$f" /opt/tomcat/conf/tls/$f
done
systemctl restart tomcat

Tomcat con archivos PEM

Tomcat 9, 10 y 11 leen PEM directamente; no hace falta keystore. En conf/server.xml:

<Connector port="8443" protocol="org.apache.coyote.http11.Http11NioProtocol"
           SSLEnabled="true" maxParameterCount="1000">
  <UpgradeProtocol className="org.apache.coyote.http2.Http2Protocol" />
  <SSLHostConfig protocols="TLSv1.2+TLSv1.3">
    <Certificate certificateFile="conf/tls/cert.pem"
                 certificateKeyFile="conf/tls/privkey.pem"
                 certificateChainFile="conf/tls/chain.pem" />
  </SSLHostConfig>
</Connector>

Con certificateChainFile, Tomcat entrega también el intermedio. Para recargar el certificado sin reiniciar, usa el manager: /manager/text/sslReload. El usuario necesita el rol manager-script.

Spring Boot con un SSL bundle

Desde Spring Boot 3.1 los certificados se describen como SSL bundle. Desde 3.2, Spring Boot los recarga solo cuando cambian y el reinicio en el hook sobra:

server.port=8443
server.ssl.bundle=web
spring.ssl.bundle.pem.web.keystore.certificate=file:/opt/app/tls/fullchain.pem
spring.ssl.bundle.pem.web.keystore.private-key=file:/opt/app/tls/privkey.pem
spring.ssl.bundle.pem.web.reload-on-update=true

Spring Boot 2.7 y 3.0 aún no tienen bundles, pero PEM funciona igualmente:

server.ssl.certificate=file:/opt/app/tls/fullchain.pem
server.ssl.certificate-private-key=file:/opt/app/tls/privkey.pem

Versiones antiguas: crear un keystore

Las versiones antiguas de Tomcat o Spring Boot necesitan un keystore. El hook lo vuelve a crear en cada renovación:

openssl pkcs12 -export -in "$RENEWED_LINEAGE/fullchain.pem" -inkey "$RENEWED_LINEAGE/privkey.pem" \
  -name tomcat -out /opt/app/tls/keystore.p12 -passout pass:secreto
server.ssl.key-store=file:/opt/app/tls/keystore.p12
server.ssl.key-store-type=PKCS12
server.ssl.key-store-password=secreto
server.ssl.key-alias=tomcat

El asistente de keystores Java hace lo mismo en el navegador: pega certificado y clave y guarda como PKCS#12.

El puerto 443 sin root

Java no debería ejecutarse como root y por eso no puede abrir puertos por debajo de 1024. Lo habitual es arrancar la aplicación en 8443 y redirigir el 443 en el cortafuegos:

sudo iptables -t nat -A PREROUTING -p tcp --dport 443 -j REDIRECT --to-port 8443

Otra opción es AmbientCapabilities=CAP_NET_BIND_SERVICE en la unidad de systemd. Después comprueba cadena y protocolos con el comprobador SSL y añade el servidor al resumen de caducidad.

FAQ

¿Tengo que reiniciar Tomcat tras cada renovación?

Con Tomcat sí, o usa sslReload en el manager. Spring Boot 3.2+ con reload-on-update recarga solo los archivos modificados.

¿Funciona Let's Encrypt para servidores internos?

Sí, con el desafío DNS-01. El servidor no tiene que ser accesible desde Internet; basta un registro TXT en el DNS público.

Tomcat muestra «Cannot recover key» o «keystore password was incorrect».

Las contraseñas del keystore y de la clave son distintas o el alias no coincide. En PKCS#12 ambas contraseñas deben ser iguales.

¿Funciona una clave ECDSA con Java?

Sí, todas las versiones actuales de Java admiten ECDSA. Para clientes muy antiguos, certbot puede obtener un certificado RSA con --key-type rsa.