<!-- https://pkizerotohero.com/guides/install-certificate-tomcat/ | Markdown mirror of the HTML page | PKI Zero to Hero, by Duyutech BV -->

> Install a TLS certificate in Tomcat 9, 10.1 or 11 with PEM files or a PKCS #12 keystore, plus Spring Boot. Real output, the chain trap and the fixes.

Source: https://pkizerotohero.com/guides/install-certificate-tomcat/ · Last reviewed: 2026-10-20

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](https://pkizerotohero.com/guides/#task-install)

Platforms

- Linux
- Windows
- Java
- Tomcat

Who it is for

- [IT ops](https://pkizerotohero.com/start/#itops)
- [Developer](https://pkizerotohero.com/start/#developer)

Level

Practitioner · 6 steps

## Before you start

- Your server certificate (the [leaf certificate](https://pkizerotohero.com/handbook/glossary/#leaf-certificate)), the CA’s [intermediate certificate](https://pkizerotohero.com/handbook/glossary/#intermediate-certificate) or certificates, and the [private key](https://pkizerotohero.com/handbook/glossary/#private-key), as PEM files, or a [PKCS #12](https://pkizerotohero.com/handbook/glossary/#pkcs12) 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`:

```bash
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](https://tomcat.apache.org/tomcat-10.1-doc/config/http.html#SSL_Support)):

```xml
<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.

Common mistake

A full chain in certificateFile can lose the intermediate

Tomcat has two TLS implementations: Java’s own (JSSE) and OpenSSL (through Tomcat Native, which Ubuntu installs with `tomcat10` as a recommended package). In our test with Tomcat 10.1.55, a `fullchain.pem` in `certificateFile` worked with JSSE but sent **only the leaf** with OpenSSL: `Verify return code: 21 (unable to verify the first certificate)`. With the intermediate in `certificateChainFile`, both implementations sent the full chain. The documentation also says the chain “should not include the server certificate as its first element”.

### 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](https://pkizerotohero.com/guides/convert-pfx-pem/); from an old JKS file, see [JKS to PKCS #12](https://pkizerotohero.com/guides/convert-jks-pkcs12/)):

```bash
openssl pkcs12 -export -out www.p12 -inkey www.example.com.key -in www.example.com.crt -certfile intermediate.crt -name tomcat
```

```xml
<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

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

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

```text
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](https://docs.spring.io/spring-boot/how-to/webserver.html#howto.webserver.configure-ssl)). An SSL bundle can also reload renewed files without a restart ([SSL bundles](https://docs.spring.io/spring-boot/reference/features/ssl.html)):

```yaml
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](https://tomcat.apache.org/tomcat-10.1-doc/manager-howto.html)). With short certificate lifetimes, automate this step.

## Check it worked

From another machine if you can:

```bash
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`):

```text
 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:

*Interactive tool: Chain checker (runs in the browser at [/tools/chain/](https://pkizerotohero.com/tools/chain/))*

## If something goes wrong

- **One certificate is sent; clients report [`unable to verify the first certificate`](https://pkizerotohero.com/errors/openssl-verify-error-21/), [`unable to get local issuer certificate`](https://pkizerotohero.com/errors/curl-unable-to-get-local-issuer-certificate/) or, in Java, [`PKIX path building failed`](https://pkizerotohero.com/errors/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](https://pkizerotohero.com/guides/build-fullchain/).
- **`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`](https://pkizerotohero.com/errors/java-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](https://pkizerotohero.com/guides/check-key-matches-certificate/) 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](https://pkizerotohero.com/handbook/glossary/#tls-handshake) and proves it holds the key. The client builds a [certificate chain](https://pkizerotohero.com/handbook/glossary/#certificate-chain) from your leaf through the intermediates to a root in its own trust store. See [Certificate chains and path validation](https://pkizerotohero.com/handbook/certificate-chains/), [The TLS 1.3 handshake](https://pkizerotohero.com/handbook/tls-handshake/) and [Certificate file formats](https://pkizerotohero.com/handbook/file-formats/).

## Go deeper

- [Tomcat 10.1: SSL support in the HTTP connector](https://tomcat.apache.org/tomcat-10.1-doc/config/http.html#SSL_Support): every `SSLHostConfig` and `Certificate` attribute.
- [Tomcat SSL/TLS how-to](https://tomcat.apache.org/tomcat-10.1-doc/ssl-howto.html): keystores, PEM files and the two implementations.
- [Spring Boot: SSL bundles](https://docs.spring.io/spring-boot/reference/features/ssl.html): PEM bundles and reloading.

**Early edition.** Checked against the standards; an independent expert review is in progress. Found a mistake? [Tell us](https://pkizerotohero.com/contact/?topic=content-error&page=/guides/install-certificate-tomcat/).

Last reviewed 20 October 2026Edition 0.9

## Keep going

### Learn why it works

- [Chapter 13: Certificate chains and path validation](https://pkizerotohero.com/handbook/certificate-chains/)
- [Chapter 15: The TLS 1.3 handshake](https://pkizerotohero.com/handbook/tls-handshake/)
- [File formats: PEM, DER, PKCS #12 and friends](https://pkizerotohero.com/handbook/file-formats/)

### Error pages

- [verify error:num=21:unable to verify the first certificate](https://pkizerotohero.com/errors/openssl-verify-error-21/)
- [PKIX path building failed … unable to find valid certification path to requested target](https://pkizerotohero.com/errors/java-pkix-path-building-failed/)
- [Received fatal alert: handshake\_failure](https://pkizerotohero.com/errors/java-handshake-failure/)
- [SSL certificate problem: unable to get local issuer certificate](https://pkizerotohero.com/errors/curl-unable-to-get-local-issuer-certificate/)

### Tools

- [Chain checker](https://pkizerotohero.com/tools/chain/): Paste a chain and see which link is missing, expired or out of order.
- [Certificate decoder](https://pkizerotohero.com/tools/decoder/): Paste a PEM certificate and read every field in plain words.

### Topics

- [TLS and HTTPS](https://pkizerotohero.com/topics/tls/)

## More guides: Install on a server

- [How to build the full chain file your server needs](https://pkizerotohero.com/guides/build-fullchain/)
- [How to install a certificate in nginx](https://pkizerotohero.com/guides/install-certificate-nginx/)
- [How to install a certificate in Apache httpd](https://pkizerotohero.com/guides/install-certificate-apache/)
- [How to install a certificate in IIS](https://pkizerotohero.com/guides/install-certificate-iis/)
