Configure Tomcat SSL Certificate with keytool Keystore

Enable Tomcat 10 HTTPS on port 8443 with a PKCS12 keystore from keytool: import the CA chain before the server certificate, set certificateKeystoreFile in server.xml, and verify the TLS handshake.

Published

Updated

Read time 7 min read

Reviewed byDeepak Prasad

Tomcat SSL with keytool PKCS12 keystore and HTTPS connector banner

Apache Tomcat terminates HTTPS with a Java keystore — the same PKCS12 files you build with keytool for Spring Boot or standalone Java services. Tomcat 10 expects the keystore path and passwords on the <Connector> element in conf/server.xml, not in a Spring-style application.properties file.

This guide builds a PKCS12 keystore for Tomcat 10, imports the CA chain before the signed server certificate (order matters), configures port 8443, and verifies TLS with the same truststore pattern used in the Spring Boot lab.

Tested on: Ubuntu 26.04 LTS; OpenJDK 25.0.3; Apache Tomcat 10.1.40; kernel 7.0.0-27-generic.


Prerequisites

  • keytool from OpenJDK — Install keytool on Ubuntu covers Ubuntu packages and JAVA_HOME.
  • Apache Tomcat 10.x extracted or installed under /opt/tomcat (adjust paths to your layout).
  • A free TCP port — this lab uses 8443 on 127.0.0.1.
  • Basic familiarity with keystore vs truststore — Tomcat loads a keystore for server identity; browsers and Java clients use their own truststores.

Build a PKCS12 keystore for Tomcat

Tomcat needs a PrivateKeyEntry alias (this lab uses https) plus any CA certificates required to present a full chain to clients. The steps mirror the Spring Boot HTTPS guide but write tomcat-keystore.p12.

Step 1 — Create a lab CA and key pair

Generate the signing CA and the server key pair in separate PKCS12 files:

bash
mkdir -p ~/tomcat-ssl-lab
cd ~/tomcat-ssl-lab

keytool -genkeypair -alias demo-ca -keyalg RSA -keysize 2048 -validity 3650 \
  -dname "CN=Demo Lab CA,O=Lab,C=US" -ext "bc=ca:true" \
  -keystore ca.p12 -storetype PKCS12 -storepass changeit -noprompt

keytool -genkeypair -alias https -keyalg RSA -keysize 2048 -validity 365 \
  -dname "CN=localhost,O=Lab,C=US" \
  -ext "SAN=DNS:localhost,IP:127.0.0.1" \
  -keystore tomcat-keystore.p12 -storetype PKCS12 -storepass changeit -noprompt

keytool -list -keystore tomcat-keystore.p12 -storetype PKCS12 -storepass changeit
text
Keystore type: PKCS12
Keystore provider: SUN

Your keystore contains 1 entry

https, Jul 2, 2026, PrivateKeyEntry,
Certificate fingerprint (SHA-256): 9F:61:99:E4:26:B4:D7:FC:9C:E0:BB:A5:23:67:8A:71:FF:CC:18:83:D4:2A:70:88:35:42:CD:7E:90:53:01:36

At this stage the entry is self-signed. The next steps replace it with a CA-signed certificate.

Step 2 — Sign the server certificate

Export a CSR from the https alias, sign it with the lab CA, and keep the result as tomcat.crt:

bash
keytool -certreq -alias https \
  -keystore tomcat-keystore.p12 \
  -storetype PKCS12 \
  -storepass changeit \
  -file tomcat.csr \
  -ext "SAN=DNS:localhost,IP:127.0.0.1"

keytool -gencert -alias demo-ca -infile tomcat.csr -outfile tomcat.crt \
  -keystore ca.p12 -storetype PKCS12 -storepass changeit -validity 365 \
  -ext "SAN=DNS:localhost,IP:127.0.0.1" \
  -ext "EKU=serverAuth"

keytool -printcert -file tomcat.crt | grep -E 'Owner:|Valid from:'
text
Owner: CN=localhost, O=Lab, C=US
Valid from: Thu Jul 02 22:10:10 IST 2026 until: Fri Jul 02 22:10:10 IST 2027

For the local keytool -gencert lab, SAN is added again during signing so the final certificate contains the extension. For a real PKI workflow, include SAN in the CSR and confirm your CA copies or applies the requested SAN values.

Use DNS: for hostnames and IP: for numeric addresses — do not put 127.0.0.1 under DNS:. The SAN extension must include the hostname clients use (localhost or your DNS name) or Java clients fail hostname verification even when the chain is trusted.

Step 3 — Import CA chain before server certificate

IMPORTANT
Import the CA certificate into tomcat-keystore.p12 before you install the signed server reply. Reversing the order often triggers keytool error: java.lang.Exception: Failed to establish chain from reply.

Export the CA once, then import from the file:

bash
keytool -exportcert -alias demo-ca \
  -keystore ca.p12 -storetype PKCS12 -storepass changeit \
  -rfc -file demo-ca.crt

keytool -importcert -alias demo-ca -file demo-ca.crt \
  -keystore tomcat-keystore.p12 -storetype PKCS12 -storepass changeit -noprompt

keytool -importcert -alias https -file tomcat.crt \
  -keystore tomcat-keystore.p12 -storetype PKCS12 -storepass changeit -noprompt

Verify entries:

bash
keytool -list -keystore tomcat-keystore.p12 -storetype PKCS12 -storepass changeit
text
Keystore type: PKCS12
Keystore provider: SUN

Your keystore contains 2 entries

demo-ca, Jul 2, 2026, trustedCertEntry,
Certificate fingerprint (SHA-256): 82:55:84:43:0B:3A:73:DC:63:DC:E8:C7:EF:A7:98:39:FE:AA:AE:E1:84:82:95:56:2B:48:87:A1:0A:C5:1B:02
https, Jul 2, 2026, PrivateKeyEntry,
Certificate fingerprint (SHA-256): 92:E0:58:15:04:44:05:4B:7E:52:04:A1:F3:69:8A:4F:C4:81:4B:8E:87:ED:E5:3E:C4:14:74:D1:36:D0:96:F8

demo-ca as trustedCertEntry gives keytool the CA material needed to build the chain during import. The https alias then holds the PrivateKeyEntry and signed certificate chain Tomcat presents to clients.

For production CAs, follow CSR and import CA-signed certificate with keytool instead of gencert.


Configure the Tomcat HTTPS Connector

Tomcat reads the keystore path from server.xml, not from Spring-style properties. Copy the PKCS12 file into catalina.base and restrict permissions so only the tomcat user can read the private key:

bash
sudo cp tomcat-keystore.p12 /opt/tomcat/conf/tomcat-keystore.p12
sudo chown tomcat:tomcat /opt/tomcat/conf/tomcat-keystore.p12
sudo chmod 600 /opt/tomcat/conf/tomcat-keystore.p12

Add or uncomment an HTTPS connector in conf/server.xml. Tomcat 10.1 uses NIO connector attributes under SSLHostConfig:

xml
<Connector port="8443"
           protocol="org.apache.coyote.http11.Http11NioProtocol"
           maxThreads="150"
           SSLEnabled="true"
           scheme="https"
           secure="true">
  <SSLHostConfig>
    <Certificate certificateKeystoreFile="${catalina.base}/conf/tomcat-keystore.p12"
                 certificateKeystorePassword="changeit"
                 certificateKeystoreType="PKCS12"
                 certificateKeyAlias="https" />
  </SSLHostConfig>
</Connector>
Attribute Value in this lab
certificateKeystoreFile Path to PKCS12 under catalina.base
certificateKeystorePassword changeit (externalize in production)
certificateKeystoreType PKCS12
certificateKeyAlias https — must match PrivateKeyEntry from keytool -list
WARNING
Plaintext passwords in server.xml are acceptable for a local lab only. For production, use protected configuration, environment-specific property substitution, filesystem permissions, or a secrets manager. Do not commit real keystore passwords to Git.

Disable the plain HTTP connector on port 8080 if your policy requires HTTPS-only access.


Start Tomcat and verify TLS

Start Tomcat and watch catalina.out for connector initialization errors:

bash
/opt/tomcat/bin/catalina.sh start
tail -20 /opt/tomcat/logs/catalina.out
text
Initializing ProtocolHandler ["https-openssl-nio-8443"]
Starting ProtocolHandler ["https-...-8443"]
Server startup in [1939] milliseconds

Depending on Tomcat Native/OpenSSL availability, the log may say https-jsse-nio-8443 instead of https-openssl-nio-8443. A line like Starting ProtocolHandler ["https-...-8443"] without keystore password was incorrect means Tomcat loaded the PKCS12 file.

Build a client truststore with only the CA public certificate. Reuse demo-ca.crt from the export step above:

bash
keytool -importcert -alias demo-ca -file demo-ca.crt \
  -keystore truststore.p12 -storetype PKCS12 -storepass changeit -noprompt

Test with a Java HttpClient while Tomcat listens on 8443. Save this as ClientTest.java in the lab directory:

java
import javax.net.ssl.*;
import java.net.URI;
import java.net.http.*;
import java.nio.file.*;
import java.security.*;

public class ClientTest {
    public static void main(String[] args) throws Exception {
        KeyStore ts = KeyStore.getInstance("PKCS12");
        try (var in = Files.newInputStream(Path.of("truststore.p12"))) {
            ts.load(in, "changeit".toCharArray());
        }
        TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
        tmf.init(ts);
        SSLContext ctx = SSLContext.getInstance("TLS");
        ctx.init(null, tmf.getTrustManagers(), null);
        HttpResponse<String> r = HttpClient.newBuilder().sslContext(ctx).build()
            .send(HttpRequest.newBuilder(URI.create("https://127.0.0.1:8443/")).GET().build(),
                HttpResponse.BodyHandlers.ofString());
        System.out.println("HTTP status: " + r.statusCode());
    }
}
bash
javac ClientTest.java && java ClientTest
text
HTTP status: 200

curl without --cacert fails against a private CA (expected). Trust the exported CA explicitly for a quick command-line check:

bash
curl --cacert demo-ca.crt -s -o /dev/null -w '%{http_code}\n' https://127.0.0.1:8443/
text
200

An HTTP 200 from curl confirms the TLS handshake and Tomcat default page both succeed when the client trusts the lab CA.


Troubleshooting

These errors map directly to keystore contents or server.xml attribute typos:

Error Fix
Failed to establish chain from reply Import CA/intermediate certs before the signed server cert — see Import certificate chain with keytool
keystore password was incorrect Match certificateKeystorePassword to the PKCS12 store password
Alias <name> does not identify a key entry certificateKeyAlias must name a PrivateKeyEntry, not a trustedCertEntry
PKIX path building failed on the client Client truststore missing the lab CA — import with keytool -importcert into a custom file
Hostname verification failure Add DNS:localhost and IP:127.0.0.1 SAN at cert generation — localhost SAN guide

Wrong password on the command line reproduces the same root cause Tomcat logs at startup:

bash
keytool -list -keystore tomcat-keystore.p12 -storetype PKCS12 -storepass badpass
text
keytool error: java.io.IOException: keystore password was incorrect

References


Summary

Tomcat HTTPS on port 8443 comes down to a PKCS12 keystore from keytool, correct import order (CA chain before the signed server certificate), and a server.xml connector with certificateKeystoreFile, certificateKeystorePassword, certificateKeystoreType=PKCS12, and certificateKeyAlias set to your PrivateKeyEntry. I confirmed the keystore lists https as PrivateKeyEntry, curl --cacert demo-ca.crt returns HTTP 200, and a Java client with a separate truststore reaches HTTP 200 on https://127.0.0.1:8443/. For shipping trust to containers without touching cacerts, continue with Java truststore in Docker and Kubernetes.


Frequently Asked Questions

1. How do I configure SSL on Tomcat with keytool?

Create a PKCS12 keystore with keytool, import the CA certificate before the signed server cert, then add an HTTPS Connector in conf/server.xml with certificateKeystoreFile, certificateKeystorePassword, certificateKeystoreType=PKCS12, and certificateKeyAlias matching your PrivateKeyEntry alias.

2. What is certificateKeystoreFile in Tomcat?

It is the path to the Java keystore (PKCS12 or JKS) that holds the Tomcat TLS private key and certificate chain. Tomcat 10 uses the NIO connector attributes certificateKeystoreFile and certificateKeystorePassword instead of the older keystoreFile name on some legacy examples.

3. Why must I import the CA chain before the server certificate in Tomcat?

Import the CA certificate before the signed server certificate so keytool can build the chain when installing the certificate reply. After import, the Tomcat server alias should be a PrivateKeyEntry with the signed certificate chain.

4. Can Tomcat use the same keystore as Spring Boot?

Yes. Both are Java stacks that load PKCS12 via KeyStore APIs. Use the same file and alias names if you migrate from embedded Spring Boot to standalone Tomcat.

5. What alias should I use for Tomcat HTTPS?

Any alias works if certificateKeyAlias matches. This guide uses https as the PrivateKeyEntry alias, which matches common Spring Boot examples.

6. How do I verify Tomcat SSL is working?

Run keytool -list on the keystore to confirm PrivateKeyEntry, start Tomcat, then curl --cacert demo-ca.crt https://127.0.0.1:8443/ or use a Java HttpClient with a truststore that contains your signing CA.
Deepak Prasad

R&D Engineer

Founder of GoLinuxCloud with more than 15 years of expertise in Linux, Python, Go, Laravel, DevOps, Kubernetes, Git, Shell scripting, OpenShift, AWS, Networking, and Security. With extensive …