Import Root, Intermediate, and Server Certificate Chain with keytool

Import a TLS chain into a Java PKCS12 keystore with keytool: root CA and intermediate as trustedCertEntry, server reply on the PrivateKeyEntry alias, correct order, -trustcacerts, and chain verification with keytool -list -v.

Published

Updated

Read time 7 min read

Reviewed byDeepak Prasad

keytool import root intermediate server certificate chain banner

Java TLS servers need more than a single .crt file in a PKCS12 keystore. When your certificate is signed by an intermediate CA, keytool must find that intermediate (and sometimes the root) inside the keystore to build a valid chain. Import order matters: anchors first, server reply last on the key alias.

This guide builds a three-tier lab chain (root → intermediate → server), imports each layer with keytool -importcert, shows what a healthy keystore looks like in keytool -list -v, and demonstrates what happens when you skip the intermediate. I ran the workflow on Ubuntu with OpenJDK 25 and OpenSSL for CA signing.

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


Prerequisites

You need a working keytool install, lab CA files, and a clear picture of which file is identity vs trust:


Chain roles and aliases

Each file in a three-tier PKI maps to a distinct keystore role. Keep CA aliases separate from the server key alias:

File Keystore role Suggested alias Entry type
root-ca.crt Lab trust anchor; often omitted from the served TLS chain in production rootca trustedCertEntry
intermediate-ca.crt Issuer of server cert intermediateca trustedCertEntry
server-signed.crt Leaf TLS certificate server (same as key alias) Updates PrivateKeyEntry chain

In production, servers usually send the leaf and intermediate certificates; clients already have the root. In this lab, importing the root makes keytool chain validation and verification easier.

The server private key is created first with keytool -genkeypair. CA certificates are public — import them with -importcert and no private key.


Step 1 — Create the server key pair

Generate the TLS identity first. The alias you choose here (server) must receive the signed reply later:

bash
mkdir -p ~/keytool-lab/server
cd ~/keytool-lab/server

keytool -genkeypair \
  -alias server \
  -keyalg RSA \
  -keysize 2048 \
  -validity 365 \
  -dname "CN=app.lab.local, OU=Lab, O=GoLinuxCloud, C=US" \
  -ext "SAN=DNS:app.lab.local,DNS:localhost,IP:127.0.0.1" \
  -keystore server-chain.p12 \
  -storetype PKCS12 \
  -storepass changeit \
  -noprompt

server-chain.p12 now holds a self-signed PrivateKeyEntry on alias server — a placeholder until the CA signs your CSR.


Step 2 — Import root CA

Import the root as a trustedCertEntry before any leaf reply. This gives keytool an anchor when it validates the chain:

bash
keytool -importcert \
  -alias rootca \
  -file ../ca/root-ca.crt \
  -keystore server-chain.p12 \
  -storetype PKCS12 \
  -storepass changeit \
  -noprompt
text
Certificate was added to keystore

The root is stored as a public trust anchor — no private key is involved.


Step 3 — Import intermediate CA

Add the intermediate that will sign the server certificate:

bash
keytool -importcert \
  -alias intermediateca \
  -file ../ca/intermediate-ca.crt \
  -keystore server-chain.p12 \
  -storetype PKCS12 \
  -storepass changeit \
  -noprompt

keytool should print Certificate was added to keystore for the intermediate as well.

List all entries — you should see two trustedCertEntry rows and one PrivateKeyEntry:

bash
keytool -list -keystore server-chain.p12 -storetype PKCS12 -storepass changeit
text
Keystore type: PKCS12

Your keystore contains 3 entries

intermediateca, Jul 2, 2026, trustedCertEntry,
rootca, Jul 2, 2026, trustedCertEntry,
server, Jul 2, 2026, PrivateKeyEntry,

Three entries means the keystore is ready for the CSR and signed reply.

Verbose view of the intermediate confirms it chains to the root you just imported:

bash
keytool -list -v -keystore server-chain.p12 -storetype PKCS12 -storepass changeit -alias intermediateca
text
Alias name: intermediateca
Entry type: trustedCertEntry

Owner: C=US, O=GoLinuxCloud, CN=Lab Intermediate CA
Issuer: C=US, O=GoLinuxCloud, CN=Lab Root CA

The intermediate's issuer matches the root subject — the first two tiers are linked inside the keystore.


Step 4 — CSR, sign, and import the server certificate

Export the CSR from alias server with SAN extensions, sign it with the intermediate CA, then import the reply on the same alias:

bash
keytool -certreq \
  -alias server \
  -keystore server-chain.p12 \
  -storetype PKCS12 \
  -storepass changeit \
  -file server.csr \
  -ext "SAN=DNS:app.lab.local,DNS:localhost,IP:127.0.0.1"

cat > server-ext.cnf <<'EOF'
basicConstraints=critical,CA:FALSE
keyUsage=critical,digitalSignature,keyEncipherment
extendedKeyUsage=serverAuth
subjectAltName=DNS:app.lab.local,DNS:localhost,IP:127.0.0.1
EOF

openssl x509 -req -in server.csr \
  -CA ../ca/intermediate-ca.crt \
  -CAkey ../ca/intermediate-ca.key \
  -CAcreateserial \
  -out server-signed.crt \
  -days 365 -sha256 \
  -extfile server-ext.cnf

Confirm SAN survived signing:

bash
openssl x509 -in server-signed.crt -noout -text | grep -A2 "Subject Alternative"
text
X509v3 Subject Alternative Name: 
                DNS:app.lab.local, DNS:localhost, IP Address:127.0.0.1

Import the signed reply:

bash
keytool -importcert -alias server -file server-signed.crt \
  -keystore server-chain.p12 -storetype PKCS12 -storepass changeit -noprompt

Success means keytool replaced the self-signed placeholder with the CA-signed certificate:

text
Certificate reply was installed in keystore

Check chain metadata on the server entry:

bash
keytool -list -v \
  -keystore server-chain.p12 \
  -storetype PKCS12 \
  -storepass changeit \
  -alias server | grep -E "Entry type|Certificate chain length|Owner:|Issuer:"
text
Entry type: PrivateKeyEntry
Certificate chain length: 3
Owner: CN=app.lab.local, OU=Lab, O=GoLinuxCloud, C=US
Issuer: CN=Lab Intermediate CA, O=GoLinuxCloud, C=US

The server alias should now contain the CA-signed certificate chain (leaf → intermediate → root), not only the self-signed placeholder.


Using -trustcacerts

keytool already checks trusted certificates in the target keystore when importing a certificate reply. Add -trustcacerts when you also want keytool to consult the JDK cacerts file. This helps for public CAs already trusted by the JDK, but it does not replace missing private CA certificates.

When CA certificates already live in cacerts (public CAs) or were imported into your keystore, retry the server import with -trustcacerts:

bash
keytool -importcert -trustcacerts \
  -alias server -file server-signed.crt \
  -keystore server-chain.p12 -storetype PKCS12 -storepass changeit -noprompt

For private lab CAs, cacerts does not contain your root — import root and intermediate into the keystore explicitly instead of relying on -trustcacerts alone.


What happens when the intermediate is missing

Skipping the intermediate reproduces the most common chain import failure. Create a fresh keystore with only the key pair and try importing the signed server cert:

bash
keytool -genkeypair -alias server -keyalg RSA -keysize 2048 -validity 365 \
  -dname "CN=app.lab.local, OU=Lab, O=GoLinuxCloud, C=US" \
  -keystore omit-inter.p12 -storetype PKCS12 -storepass changeit -noprompt

keytool -certreq -alias server -keystore omit-inter.p12 -storetype PKCS12 \
  -storepass changeit -file omit.csr

openssl x509 -req -in omit.csr -CA ../ca/intermediate-ca.crt \
  -CAkey ../ca/intermediate-ca.key -CAcreateserial \
  -out omit-signed.crt -days 365 -sha256

keytool -importcert -alias server -file omit-signed.crt \
  -keystore omit-inter.p12 -storetype PKCS12 -storepass changeit -noprompt

keytool cannot link the reply to a known issuer:

text
keytool error: java.lang.Exception: Failed to establish chain from reply

Fix by importing intermediate-ca.crt (and root-ca.crt if required) before the server reply. Full troubleshooting is in Fix keytool Failed to establish chain from reply.


Client truststore vs server keystore

The server keystore contains the private key alias and the certificate chain that the TLS server presents to clients. Client truststores are separate and should contain only public CA certificates.

Export the intermediate for client trust bundles:

bash
keytool -exportcert -alias intermediateca -keystore server-chain.p12 \
  -storetype PKCS12 -storepass changeit -rfc -file intermediate-for-clients.crt

Import that PEM into a client-only PKCS12 with keytool -importcert — never copy the server keystore to clients.


Troubleshooting

Use this table when chain import or browser verification fails after the steps above:

Symptom Likely cause Fix
Failed to establish chain from reply Missing intermediate or root in keystore Import CA certs before leaf; see fix guide
Duplicate alias on CA import Re-importing same alias Use keytool -delete -alias rootca or pick new names
Chain length remains 1 or issuer is self-signed Server reply was not installed Re-run -importcert on the original PrivateKeyEntry alias and check stderr
Browser shows incomplete chain Server not sending intermediate Include intermediate in keystore; configure connector to send chain
Wrong entry type for CA Imported server cert as new alias CA files must be trustedCertEntry, not new PrivateKeyEntry

References


Summary

Import root and intermediate CA certificates as separate trustedCertEntry aliases, then import the CA-signed server reply on the PrivateKeyEntry alias that owns the private key. Use keytool -list to confirm three entries and keytool -list -v -alias server to verify Certificate chain length: 3 with issuer Lab Intermediate CA. Skipping the intermediate produces Failed to establish chain from reply — add the missing CA certificates and import the leaf again.


Frequently Asked Questions

1. What order should I import certificates with keytool?

Import trust anchors first: root CA, then intermediate CA, each as trustedCertEntry with distinct aliases. Import the server or leaf certificate last with -importcert on the PrivateKeyEntry alias that holds the matching private key.

2. What does keytool -trustcacerts do during import?

The -trustcacerts flag also lets keytool consult the JDK cacerts file when importing a certificate reply. keytool already checks trusted certificates in the target keystore; -trustcacerts helps when a public CA root is already trusted by the JDK, but it does not replace missing private CA certificates.

3. Should root and intermediate CAs use the same alias as the server?

No. Give each CA its own alias such as rootca and intermediateca as trustedCertEntry. Only the server leaf shares the PrivateKeyEntry alias created by -genkeypair.

4. Can I import a .p7b PKCS#7 bundle with keytool?

Yes. keytool -importcert can import a PKCS#7 certificate reply when it contains the leaf certificate and chain for the same PrivateKeyEntry alias. If the .p7b import fails or you want to inspect the bundle, split it with openssl pkcs7 -print_certs, then import missing CA certificates first and the leaf reply on the original key alias.

5. How do I verify the chain length in keytool?

Run keytool -list -v -keystore store.p12 -storetype PKCS12 -alias SERVER_ALIAS and check Certificate chain length on the PrivateKeyEntry. After a successful server reply import, the length is often 3 (leaf, intermediate, root). Also list the whole keystore to confirm root and intermediate trustedCertEntry rows exist.

6. Do I import the chain into a keystore or truststore?

Server keystores need the private key alias plus the certificate chain the TLS server presents to clients. Client truststores need only public CA certificates as trustedCertEntry. See keystore vs truststore for role separation.
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 …