Which way fits?
- Reverse proxy in front: nginx, Apache or Caddy handle TLS, the Java application runs internally on HTTP. Usually the simplest way; certificates and renewal stay with the proxy. The TLS config generator creates a matching configuration.
- Tomcat or Spring Boot directly: useful without a proxy, for small services or containers. That is what this article covers.
Get the certificate
Let's Encrypt validates via HTTP-01 on port 80 or via DNS-01. If nothing runs on port 80, certbot’s standalone mode works; otherwise use webroot or DNS. The ACME helper gives you the commands. Afterwards the files are in /etc/letsencrypt/live/example.com/:
cert.pem,chain.pem,fullchain.pem: certificate, intermediate, and both togetherprivkey.pem: the private key. Since version 2.0 certbot creates ECDSA keys by default.
The permission trap
The live and archive directories are readable by root only. Tomcat and Spring Boot run as their own user and get “Permission denied”. Instead of loosening permissions, a deploy hook copies the files after each renewal to a place the application may read:
#!/bin/sh # /etc/letsencrypt/renewal-hooks/deploy/tomcat.sh (make executable) 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 with PEM files
Tomcat 9, 10 and 11 read PEM directly; no keystore needed. In 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>
With certificateChainFile Tomcat also delivers the intermediate. To reload the certificate without a restart, use the manager: /manager/text/sslReload. The user needs the manager-script role.
Spring Boot with an SSL bundle
From Spring Boot 3.1 you describe certificates as an SSL bundle. From 3.2 Spring Boot reloads them itself when they change, so the restart in the hook is not needed:
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 and 3.0 have no bundles yet, but PEM works anyway:
server.ssl.certificate=file:/opt/app/tls/fullchain.pem server.ssl.certificate-private-key=file:/opt/app/tls/privkey.pem
Older versions: create a keystore
Older Tomcat or Spring Boot versions need a keystore. The hook then recreates it at every renewal:
openssl pkcs12 -export -in "$RENEWED_LINEAGE/fullchain.pem" -inkey "$RENEWED_LINEAGE/privkey.pem" \ -name tomcat -out /opt/app/tls/keystore.p12 -passout pass:secret
server.ssl.key-store=file:/opt/app/tls/keystore.p12 server.ssl.key-store-type=PKCS12 server.ssl.key-store-password=secret server.ssl.key-alias=tomcat
The Java keystore helper does the same in the browser: paste certificate and key, save as PKCS#12.
Port 443 without root
Java should not run as root and therefore cannot open ports below 1024. The usual way is to start the application on 8443 and forward 443 in the firewall:
sudo iptables -t nat -A PREROUTING -p tcp --dport 443 -j REDIRECT --to-port 8443
Alternatively set AmbientCapabilities=CAP_NET_BIND_SERVICE in the systemd unit. Then check chain and protocols with the SSL check and add the server to the certificate expiry overview.
FAQ
Do I have to restart Tomcat after every renewal?
With Tomcat yes, or use sslReload in the manager. Spring Boot 3.2+ with reload-on-update reloads changed files itself.
Does Let's Encrypt work for internal servers?
Yes, with the DNS-01 challenge. The server does not have to be reachable from the internet; only a TXT record in public DNS is needed.
Tomcat says “Cannot recover key” or “keystore password was incorrect”.
Keystore and key passwords differ, or the alias is wrong. With PKCS#12 both passwords must be the same.
Does an ECDSA key work with Java?
Yes, all current Java versions support ECDSA. For very old clients certbot can get an RSA certificate with --key-type rsa.