A practical guide to completing your TLS certificate chain

TL;DR
Spring Boot Admin showed my application as REGISTERED → OFFLINE because the server certificate I presented during the health‑check was missing the intermediate CA.
Re‑exporting the keystore (PKCS #12 / JKS) with the full chain—leaf + intermediate (+ root)—cleared the error and the instance now sits happily in the UP state.

The Symptom

Right after deployment my Spring Boot app registered with Spring Boot Admin (SBA):

vbnetCopyEditEvent    : REGISTERED

Seconds later it flipped to OFFLINE:

luaCopyEditSTATUS_CHANGED (OFFLINE)
org.springframework.web.reactive.function.client.WebClientRequestException:
  PKIX path building failed:
  sun.security.provider.certpath.SunCertPathBuilderException:
  unable to find valid certification path to requested target

Translation: SBA couldn’t trust the certificate my app delivered during the /actuator/health poll.


Why SBA trusts the first call but not the second

DirectionTLS clientTLS serverResult
App → SBA (registration)Spring Boot AppSpring Boot AdminPasses – chain is fine in SBA’s keystore
SBA → App (health‑check)Spring Boot AdminSpring Boot AppFails – chain is incomplete in App’s keystore

SBA’s JVM already trusted the root CA that signed my cert.
What it didn’t receive was the intermediate CA sitting between the leaf and the root.


Diagnosing the chain

bashCopyEdit# List the current PFX/JKS
keytool -list -v -keystore processing.pfx -storepass ***** \
  | grep 'Certificate chain' -A4

# Length = 1   ← only the leaf certificate present

If you need to inspect a PEM bundle:

bashCopyEditgrep -c "BEGIN CERTIFICATE" processing.pem   # got 3? split them:
awk '/BEGIN CERT/{i++}{print > "part" i ".pem"}' processing.pem

part1 should be the server cert; part2, part3 the intermediates/root.


Re‑building a full‑chain PKCS #12

I had:

  • dlq-processing.1086.sb.crt  — leaf
  • dlq-processing.1086.sb.p7b  — intermediate + root
  • dlq-processing.1086.sb.key  — private key
bashCopyEdit# 1. Extract the CA chain from the PKCS#7
openssl pkcs7 -print_certs \
  -in dlq-processing.1086.sb.p7b \
  -out chain.pem                # intermediate + root

# 2. Export a NEW PKCS#12 with the full chain
openssl pkcs12 -export \
  -inkey dlq-processing.1086.sb.key \
  -in   dlq-processing.1086.sb.crt \
  -certfile chain.pem \
  -name dlq-processing \
  -out dlq-processing-full.p12

(Optional) convert to JKS:

bashCopyEditkeytool -importkeystore \
  -srckeystore dlq-processing-full.p12 -srcstoretype PKCS12 \
  -destkeystore dlq-processing-full.jks -deststoretype JKS \
  -alias dlq-processing

Now:

bashCopyEditkeytool -list -v -keystore dlq-processing-full.p12 -storepass ***** \
  | grep 'Certificate chain' -A4
# 3 certificates -> PrivateKeyEntry ✅

Redeploy → SBA handshake succeeds → status UP.


PEM alternative (Boot 2.7+ / 3.x)

If you prefer avoiding keystores entirely:

propertiesCopyEditserver.ssl.certificate=classpath:fullchain.pem      # leaf + intermediate + root
server.ssl.certificate-private-key=classpath:server.key

Spring Boot builds an in‑memory keystore on startup.


Key takeaways

  1. Leaf + Intermediate (+ Root)
    Always deliver the full chain; the client often has only the root in its trust‑store.
  2. -certfile is the secret sauce.
    OpenSSL’s pkcs12 -export uses only the first cert unless you add -certfile (or OpenSSL 3’s -CAfile -chain).
  3. Keystore password ≠ private key.
    A PKCS #12/JKS always needs a password; the handshake needs the private key.
  4. Trust‑store vs. key‑store.
    Trust‑store = just CA certs.
    Key‑store = private key + full chain.

The whole detour cost me a few head‑scratches, but the fix boiled down to include the intermediate CA. Hopefully this write‑up saves you that debugging loop!

LEAVE A REPLY

Please enter your comment!
Please enter your name here

This site uses Akismet to reduce spam. Learn how your comment data is processed.