How-to guide · Install on a server

How to install a certificate in Tomcat and other Java servers

In one sentence

Tomcat, or a Spring Boot or other Java server, serves your certificate with its full chain from PEM files or a PKCS #12 keystore, verified from outside.

Task
Install on a server
Platforms
  • Linux
  • Windows
  • Java
  • Tomcat
Who it is for
Level
Practitioner · 6 steps
On this page
  1. Before you start
  2. Steps
  3. 1. Prepare the files, readable by Tomcat only
  4. 2. Configure the HTTPS connector with PEM files
  5. 3. Or use a PKCS #12 keystore
  6. 4. Restart and read the log
  7. 5. Spring Boot
  8. 6. Plan for renewal
  9. Check it worked
  10. If something goes wrong
  11. Why this works
  12. Go deeper

Before you start

  • Your server certificate (the leaf certificate), the CA’s intermediate certificate or certificates, and the private key, as PEM files, or a PKCS #12 file that holds all three.
  • Tomcat 9, 10.1 or 11 on a supported Java; the attributes below are the same in all three.
  • Write access to Tomcat’s conf/server.xml and the right to restart it.

Why it matters: Java servers send the chain you configure, and Java clients do not fetch a missing intermediate, so a short chain breaks every Java, Python and Node.js client that calls your service.

Steps

1. Prepare the files, readable by Tomcat only

Keep the leaf, the chain and the key in separate files; step 2 explains why. On Ubuntu, Tomcat runs as the user tomcat:

sudo mkdir -p /etc/tomcat10/tls
sudo cp www.example.com.crt /etc/tomcat10/tls/cert.pem
sudo cp intermediate.crt    /etc/tomcat10/tls/chain.pem
sudo cp www.example.com.key /etc/tomcat10/tls/privkey.pem
sudo chown root:tomcat /etc/tomcat10/tls/*
sudo chmod 640 /etc/tomcat10/tls/privkey.pem

Unlike nginx and Apache, Tomcat reads the key as its own unprivileged user, so the key must be readable by that user and nobody else.

2. Configure the HTTPS connector with PEM files

Add a connector inside <Service> in server.xml (Tomcat HTTP connector):

<Connector port="8443" protocol="org.apache.coyote.http11.Http11NioProtocol"
           maxThreads="150" SSLEnabled="true">
    <SSLHostConfig hostName="_default_" protocols="TLSv1.2+TLSv1.3">
        <Certificate certificateFile="/etc/tomcat10/tls/cert.pem"
                     certificateChainFile="/etc/tomcat10/tls/chain.pem"
                     certificateKeyFile="/etc/tomcat10/tls/privkey.pem" />
    </SSLHostConfig>
</Connector>

protocols defaults to all, which includes TLS 1.0 and 1.1, so state it. Port 8443 is Tomcat’s convention; for 443, put a load balancer or reverse proxy in front, or give Java permission to bind low ports.

3. Or use a PKCS #12 keystore

A keystore works with Tomcat’s JSSE implementation and with most other Java servers, such as Jetty and WildFly. Build it with the chain included (PFX and PEM; from an old JKS file, see JKS to PKCS #12):

openssl pkcs12 -export -out www.p12 -inkey www.example.com.key -in www.example.com.crt -certfile intermediate.crt -name tomcat
<SSLHostConfig hostName="_default_" protocols="TLSv1.2+TLSv1.3">
    <Certificate certificateKeystoreFile="/etc/tomcat10/tls/www.p12"
                 certificateKeystorePassword="use-a-real-secret"
                 certificateKeystoreType="PKCS12" />
</SSLHostConfig>

The password sits in server.xml, so keep that file readable only by root and Tomcat as well.

4. Restart and read the log

sudo systemctl restart tomcat10
sudo journalctl -u tomcat10 | grep 'Connector \[https'

Our test logged which files each connector loaded (timestamp removed):

Connector [https-openssl-nio-29445], TLS virtual host [_default_], certificate type [UNDEFINED] configured from key [/etc/tomcat10/tls/privkey.pem], certificate [/etc/tomcat10/tls/cert.pem] and certificate chain [/etc/tomcat10/tls/chain.pem] with trust store [null]

The name shows the implementation: https-openssl-nio or https-jsse-nio. Read the log after every change: in our tests Tomcat kept running even when the HTTPS connector failed to start.

5. Spring Boot

Embedded Tomcat in Spring Boot takes PEM files directly (Spring Boot: configure SSL). An SSL bundle can also reload renewed files without a restart (SSL bundles):

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

Run the same outside check (below) on Spring Boot: we did not test how its PEM loader handles a full chain in one file.

6. Plan for renewal

Tomcat reads the files at start-up. After a renewal, restart it, or reload only the TLS files with the Manager application’s /manager/text/sslReload?tlsHostName=_default_ command (Manager how-to). With short certificate lifetimes, automate this step.

Check it worked

From another machine if you can:

openssl s_client -connect www.example.com:8443 -servername www.example.com </dev/null 2>/dev/null | grep -E '^ [0-9] s:|Verify return code'

Our test server (private test CA, so we added -CAfile corp-root.crt):

 0 s:CN = intranet.example.com
 1 s:C = BE, O = Example Corp, CN = Example Corp Issuing CA 1
Verify return code: 0 (ok)

Two certificates and code 0. Paste the -showcerts output into the chain checker:

Try it: Chain checkerOpen the full tool

Runs in your browser. Nothing you paste is sent anywhere.

Every certificate the server sends, leaf first. The output of openssl s_client -showcerts -connect example.com:443 works as it is: we ignore the text around the certificates.

If something goes wrong

  • One certificate is sent; clients report unable to verify the first certificate, unable to get local issuer certificate or, in Java, PKIX path building failed. Move the intermediates to certificateChainFile (step 2), or rebuild the keystore with -certfile (step 3). See build the full chain.
  • java.io.IOException: keystore password was incorrect in the log. The certificateKeystorePassword does not match the file.
  • java.io.FileNotFoundException: /etc/tomcat10/tls/www.p12 (Permission denied). The Tomcat user cannot read the file (step 1).
  • java.net.BindException: Address already in use. Another process holds that port.
  • Java clients report Received fatal alert: handshake_failure. No common protocol or cipher, or the connector started without a certificate; read the Tomcat log first.
  • The key does not match the certificate. Check them before you restart.

Why this works

Tomcat builds a TLS context per SSLHostConfig from the certificate, chain and key you name, then presents that chain in the TLS handshake and proves it holds the key. The client builds a certificate chain from your leaf through the intermediates to a root in its own trust store. See Certificate chains and path validation, The TLS 1.3 handshake and Certificate file formats.

Go deeper

Early edition. Checked against the standards; an independent expert review is in progress. Found a mistake? Tell us.

Last reviewed Edition 0.9