Back to blog
TomcatSpring BootCertificate Deployment

Still Renewing Tomcat and Spring Boot Certificates by Hand? How to Automate Renewal and Deployment

Tomcat and Spring Boot load certificates from a keystore, so every renewal means converting files, replacing the keystore and restarting. How to configure both, the three errors that break a swap, and how to automate it.

2026-10-309 min
A certificate converted into a PKCS12 keystore, deployed to Tomcat and Spring Boot, then checked on the live endpoint

With Nginx, a renewed certificate is two PEM files and a reload. Tomcat and Spring Boot usually load a keystore instead (.jks or .p12), so each renewal also means downloading the Tomcat-format certificate from your CA or cloud console, or running openssl pkcs12 or keytool to build a keystore yourself, then replacing the old file and restarting the app. That's fine once a year. Public certificates issued today are already capped at 200 days, the cap drops to 100 days on March 15, 2027, and to 47 days on March 15, 2029. At that point you are doing it roughly every month.

This post covers how to automate renewal for Java apps: first check whether the app loads the certificate at all, then the Tomcat and Spring Boot settings that matter, the three errors that most often break a certificate swap, and two ways to automate the whole thing: a Certbot hook you maintain yourself, or LapseZero as a hosted service.

Check where HTTPS terminates

Many Java apps sit behind Nginx, HAProxy or a cloud load balancer that handles TLS, while Tomcat listens on plain HTTP on port 8080. In that setup the certificate has nothing to do with Java. Renew it the way you would for Nginx or the load balancer and skip the keystore work.

To check: in Tomcat, look for a Connector with an SSLHostConfig in conf/server.xml. In Spring Boot, look for server.ssl in application.yml, application.properties or the startup arguments. If neither is there, the Java app usually doesn't load a certificate. The rest of this post assumes it does.

Tomcat

Since Tomcat 8.5 (including 9, 10 and 11), TLS settings go in an SSLHostConfig inside the Connector:

xml
<Connector port="8443" protocol="org.apache.coyote.http11.Http11NioProtocol" SSLEnabled="true">
  <SSLHostConfig>
    <Certificate certificateKeystoreFile="/opt/tomcat/ssl/tomcat.p12"
                 certificateKeystorePassword="changeit"
                 certificateKeystoreType="PKCS12" />
  </SSLHostConfig>
</Connector>

For automation, four attributes on Certificate matter:

  • certificateKeystoreFile is where the renewed keystore has to be written.
  • certificateKeystorePassword has to match the password the new keystore is built with.
  • certificateKeystoreType defaults to JKS when omitted. Use PKCS12 and set it explicitly.
  • certificateKeyAlias is left out above. Without it, Tomcat uses the first key it reads from the keystore, which is the simplest option when the keystore holds a single certificate.

You can also skip keystores. Tomcat 8.5 and later read PEM files directly through certificateFile, certificateKeyFile and certificateChainFile. There is no need to switch an existing keystore setup to PEM just to automate it; both work. The HTTP Connector reference lists every attribute.

Once the file is replaced, Tomcat has to load it. A restart is the simplest way, and HTTPS is down while it runs: on a small test VM, a normal Tomcat 10.1 restart took HTTPS offline for about 7 seconds. To avoid the gap, the Manager app has an sslReload command that re-reads the certificate and key files without re-parsing server.xml:

bash
curl -fsS -u deployer:password "http://localhost:8080/manager/text/sslReload" | grep -q '^OK'

This needs the Manager app deployed and a user with the manager-script role. When the reload fails, Manager answers with text starting with FAIL and curl still exits 0, so the grep is what turns a failed reload into a non-zero exit code.

Spring Boot

Spring Boot runs an embedded Tomcat and reads the certificate from its own configuration:

yaml
server:
  port: 8443
  ssl:
    key-store: file:/opt/app/ssl/app.p12
    key-store-type: PKCS12
    key-store-password: changeit

This works the same in Spring Boot 2.7 and 3.x. Watch the file: prefix. A lot of tutorials use classpath:keystore.p12, which bundles the keystore into the jar. Then a new certificate means a new build and a new release, and nothing on the server can replace it. Move the keystore out of the jar and point key-store at an absolute file: path before automating anything.

Spring Boot also accepts PEM files: server.ssl.certificate for the chain and server.ssl.certificate-private-key for the key.

From Spring Boot 3.2 you can swap certificates without a restart. Define the certificate as an SSL bundle with reload-on-update enabled and Spring Boot watches the files in the background, reloading them when they change:

yaml
spring:
  ssl:
    bundle:
      jks:
        web:
          reload-on-update: true
          keystore:
            location: file:/opt/app/ssl/app.p12
            password: changeit
            type: PKCS12
server:
  ssl:
    bundle: web

The bundle type is called jks, but it holds PKCS12 files too; type decides. server.ssl.bundle can't be combined with server.ssl.key-store and the other keystore properties, so remove those when you switch. On versions before 3.2, a restart is the only option. Spring Boot also starts slower than plain Tomcat: on the same test VM, a minimal Spring Boot 3.5 app was unreachable over HTTPS for about 15 seconds during a restart, and real applications usually take longer.

Three errors that break a certificate swap

The alias doesn't match

Many Tomcat guides create the keystore with keytool -alias tomcat and set certificateKeyAlias="tomcat" (or server.ssl.key-alias: tomcat in Spring Boot). Build the renewed keystore with a different tool and the alias may change. On the next start the app fails with:

java.io.IOException: Alias name [tomcat] does not identify a key entry

You won't see this while the app is running, because it keeps the old certificate in memory. It shows up on the first restart after renewal, when the app refuses to start. Either build the new keystore with the same alias (openssl pkcs12 -export -name tomcat), or, if the keystore only ever holds one certificate, remove the alias from the config.

The password doesn't match

The new keystore's password has to equal certificateKeystorePassword or server.ssl.key-store-password. Otherwise startup fails with:

Failed PKCS12 integrity checking

A JKS file from a CA or cloud console sometimes has a key password that differs from the keystore password, which shows up as certificateKeyPassword in Tomcat or server.ssl.key-password in Spring Boot. In a generated PKCS12 file both passwords are the same, so remove that setting or set it to the keystore password.

The restart succeeds and the app is down

This is the one that slips through. Most systemd units for Java apps use Type=simple, where systemctl restart returns as soon as the process starts, long before Java loads the certificate. With a wrong alias or password, systemctl restart still exits with 0. Tomcat keeps running without its HTTPS connector; Spring Boot exits a few seconds later. Your deploy script sees a successful command and moves on while the service is down.

So the restart step should only succeed once HTTPS actually answers, and fail otherwise:

sh
#!/bin/sh
# Usage: restart-and-wait.sh <service> <https-port>
set -eu
systemctl restart "$1"
for attempt in $(seq 1 30); do
  if curl --insecure --fail --silent --max-time 1 "https://127.0.0.1:$2/" >/dev/null; then
    exit 0
  fi
  sleep 1
done
echo "$1 did not become HTTPS-ready" >&2
exit 1

--insecure is only there because this is a local liveness check. Replace / with a path in your app that returns 200 without a login.

The first time you automate this, also check file permissions. The Java process usually runs as a non-root user, and it has to be able to read the newly written keystore.

Doing it yourself with a Certbot deploy hook

If Certbot issues the certificate on the same server, put the conversion, the swap and the restart in a deploy hook. Certbot runs it after each successful renewal and passes the certificate directory in RENEWED_LINEAGE:

sh
#!/bin/sh
set -eu
openssl pkcs12 -export \
  -in "$RENEWED_LINEAGE/fullchain.pem" \
  -inkey "$RENEWED_LINEAGE/privkey.pem" \
  -name tomcat -passout file:/etc/tomcat/keystore.pass \
  -out /opt/tomcat/ssl/tomcat.p12.new
mv /opt/tomcat/ssl/tomcat.p12.new /opt/tomcat/ssl/tomcat.p12
/usr/local/bin/restart-and-wait.sh tomcat 8443

acme.sh doesn't set RENEWED_LINEAGE. Use --install-cert to choose where the full chain and private key are written, point the script at those paths, and run it through --reloadcmd.

For one app on one server, that's enough. With more servers and apps, a few things are left to you:

  • Recovery: if the restart fails, the old keystore has already been overwritten. Keep a backup and restore it on failure, or the app stays down until someone fixes it.
  • Distribution: the renewed certificate only exists on the machine where Certbot runs. For several servers you either run Certbot and the hook on each one, or copy the certificate over and run the same steps everywhere.
  • Alerts: Certbot doesn't notify anyone by default. A failed hook only shows up in its log, so nobody knows unless someone reads it.

Deploying to Java apps with LapseZero

LapseZero is a hosted service that issues certificates, renews them before they expire and deploys them to servers and cloud services. The certificates are free DV certificates with wildcard support, validated over DNS with either a DNS provider API key or a single CNAME record. Nothing needs to be migrated: the first deployment writes the new certificate to your existing keystore path.

A Java server is set up once. Add a deployment target, choose Server, pick Tomcat or Spring Boot, and fill in three fields:

  • Certificate Path: the keystore path from your config, without the file: prefix for Spring Boot.
  • Keystore Password: the same password as in your config. If your config sets a key alias, enter that alias too.
  • Post-deploy Command: the restart-and-wait.sh script above.

LapseZero connects over SSH or through a deployment agent that needs no inbound port. Private keys and passwords are stored encrypted; the security page has the details.

From then on, every renewal builds a new PKCS12 file, writes it to the same path and runs your command, and the three errors above stop being your problem. The keystore is built with your password and alias, so they match. If the command fails, for example because the app isn't serving HTTPS within 30 seconds, LapseZero puts the old keystore back and runs the command again, so the app keeps the old certificate and the notification tells you it was rolled back. Add the app's HTTPS address under Live check and each deployment also confirms the new certificate is actually being served.

For an app on several servers, add one deployment target per server with the same settings. The Nginx, load balancer or cloud services in front of it get the same certificate from the same place, with no hook script to maintain on every machine.

The free plan covers 3 domains and 5 deployment targets with no time limit, enough to try it on one or two servers. One domain on six servers is past the free plan; Starter is $3/month for 10 domains and 20 deployment targets.

Automate Tomcat and Spring Boot certificate renewal with LapseZero
Pick Tomcat or Spring Boot and enter the keystore path, password and restart command. Every renewal then builds the PKCS12 file, writes it to your server and restarts the app, rolling back if anything fails.
Start for free