The PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target error is one of the most common issues when deploying ForgeRock Directory Services (DS) in production. It means the Java runtime cannot verify the TLS certificate chain — and until you fix it, LDAPS connections, replication, and AM-to-DS communication will all fail.
Clone the companion repo: All diagnostic and fix scripts from this guide are available at IAMDevBox/forgerock-ds-cert-troubleshoot. Clone it, configure
config.env, and run./scripts/diagnose.sh ds.example.com 1636for instant diagnosis.
Understanding the Error
The full stack trace typically looks like this:
javax.net.ssl.SSLHandshakeException: PKIX path building failed:
sun.security.provider.certpath.SunCertPathBuilderException:
unable to find valid certification path to requested target
at java.base/sun.security.ssl.Alert.createSSLException(Alert.java:131)
at java.base/sun.security.ssl.TransportContext.fatal(TransportContext.java:378)
at java.base/sun.security.ssl.TransportContext.fatal(TransportContext.java:321)
at java.base/sun.security.ssl.CertificateMessage$T13CertificateConsumer.checkServerCerts(...)
This happens at the TLS handshake layer, before any LDAP protocol exchange occurs. The client (AM, IDM, another DS, or any Java application) tried to connect over TLS and could not build a certificate chain from the server’s certificate to a trusted root CA in its truststore.
Where This Error Occurs in ForgeRock
| Connection | Protocol | Default Port | Typical Trigger |
|---|---|---|---|
| AM → DS (user store) | LDAPS | 1636 | Self-signed DS cert not in AM truststore |
| AM → DS (config store) | LDAPS | 1636 | Certificate renewed without updating AM |
| AM → DS (CTS) | LDAPS | 1636 | New DS node with different certificate |
| DS → DS (replication) | TLS | 8989 | Peer certificate not in local truststore |
| IDM → DS (connector) | LDAPS | 1636 | Connector truststore not configured |
| External LDAP client → DS | LDAPS | 1636 | Client missing CA certificate |
| DS admin tools → DS | TLS | 4444 | Admin port requires trusted cert |
Step-by-Step Diagnosis
Step 1: Identify the Exact Certificate
Use openssl to see what certificate the DS server is presenting:
# Check the certificate chain DS is presenting
openssl s_client -connect ds.example.com:1636 -showcerts </dev/null 2>/dev/null
# Extract just the server certificate details
openssl s_client -connect ds.example.com:1636 </dev/null 2>/dev/null | \
openssl x509 -text -noout
# Check expiration date
openssl s_client -connect ds.example.com:1636 </dev/null 2>/dev/null | \
openssl x509 -noout -dates
Key things to look for in the output:
Certificate chain
0 s:CN=ds.example.com
i:CN=ds.example.com ← Self-signed (subject == issuer)
---OR---
Certificate chain
0 s:CN=ds.example.com
i:CN=Example Intermediate CA ← CA-signed
1 s:CN=Example Intermediate CA
i:CN=Example Root CA ← Full chain present
If the chain shows subject == issuer, this is a self-signed certificate — the most common cause of PKIX errors.
Step 2: Check the DS Keystore
Use dskeymgr (ForgeRock DS 7.x+) to list certificates in the DS keystore:
# List all certificates in DS keystore
dskeymgr list-certificates \
--hostname localhost \
--port 4444 \
--bindDN "uid=admin" \
--bindPassword "password" \
--trustAll
# For DS 6.x and earlier, use dsconfig
dsconfig get-key-manager-provider-prop \
--provider-name "Default Key Manager" \
--hostname localhost \
--port 4444 \
--bindDN "uid=admin" \
--bindPassword "password" \
--trustAll
Step 3: Check the Client Truststore
Determine which truststore the client (AM, IDM, etc.) is using:
# Check JVM default truststore
echo $JAVA_HOME
ls -la $JAVA_HOME/lib/security/cacerts
# List trusted certificates
keytool -list -keystore $JAVA_HOME/lib/security/cacerts -storepass changeit
# Search for a specific alias
keytool -list -keystore $JAVA_HOME/lib/security/cacerts -storepass changeit | grep -i forgerock
# Check if DS certificate is trusted
keytool -list -keystore $JAVA_HOME/lib/security/cacerts -storepass changeit -alias forgerock-ds
If the DS certificate (or its CA) is not listed, that’s your problem.
Step 4: Verify the Chain
# Download the full chain
openssl s_client -connect ds.example.com:1636 -showcerts </dev/null 2>/dev/null | \
awk '/BEGIN CERTIFICATE/,/END CERTIFICATE/{print}' > full-chain.pem
# Verify the chain
openssl verify -CAfile /path/to/ca-bundle.pem full-chain.pem
# Check for missing intermediates
openssl verify full-chain.pem
# If this fails with "unable to get local issuer certificate", you're missing an intermediate CA
Cause 1: Self-Signed Certificate (Most Common)
ForgeRock DS generates a self-signed certificate during installation. This works for --trustAll connections but fails for any client doing proper certificate validation.
Fix: Import the Self-Signed Certificate
# 1. Export the DS certificate
openssl s_client -connect ds.example.com:1636 </dev/null 2>/dev/null | \
openssl x509 -out ds-cert.pem
# 2. Verify you got the right certificate
openssl x509 -in ds-cert.pem -text -noout | head -20
# 3. Import into the client's JVM truststore
keytool -importcert \
-alias forgerock-ds \
-file ds-cert.pem \
-keystore $JAVA_HOME/lib/security/cacerts \
-storepass changeit \
-noprompt
# 4. Verify the import
keytool -list -keystore $JAVA_HOME/lib/security/cacerts -storepass changeit -alias forgerock-ds
# 5. Restart the client application (AM, IDM, etc.)
Fix for Docker/Kubernetes Deployments
# Add to AM or IDM Dockerfile
COPY ds-cert.pem /tmp/ds-cert.pem
RUN keytool -importcert \
-alias forgerock-ds \
-file /tmp/ds-cert.pem \
-keystore $JAVA_HOME/lib/security/cacerts \
-storepass changeit \
-noprompt && \
rm /tmp/ds-cert.pem
For Kubernetes with cert-manager:
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: ds-cert
namespace: forgerock
spec:
secretName: ds-tls
issuerRef:
name: ca-issuer
kind: ClusterIssuer
dnsNames:
- ds.forgerock.svc.cluster.local
- ds-0.ds.forgerock.svc.cluster.local
- ds-1.ds.forgerock.svc.cluster.local
duration: 8760h # 1 year
renewBefore: 720h # Renew 30 days before expiry
Cause 2: Missing Intermediate CA Certificate
If DS uses a CA-signed certificate but the intermediate CA is not in the client’s truststore:
Certificate chain
0 s:CN=ds.example.com
i:CN=Example Intermediate CA ← Client needs this CA
but only has Example Root CA
Fix: Import the Intermediate CA
# 1. Extract the intermediate CA from the chain
openssl s_client -connect ds.example.com:1636 -showcerts </dev/null 2>/dev/null | \
awk '/BEGIN CERTIFICATE/{count++} count==2{print}' > intermediate-ca.pem
# 2. Import it
keytool -importcert \
-alias intermediate-ca \
-file intermediate-ca.pem \
-keystore $JAVA_HOME/lib/security/cacerts \
-storepass changeit \
-noprompt
# 3. Restart client
Fix: Configure DS to Send the Full Chain
The better fix is to configure DS to include the intermediate CA in its certificate chain:
# Import the full chain into DS keystore
dskeymgr import-certificate \
--hostname localhost \
--port 4444 \
--bindDN "uid=admin" \
--bindPassword "password" \
--certificate-file server-cert.pem \
--ca-certificate-file intermediate-ca.pem \
--ca-certificate-file root-ca.pem \
--alias server-cert \
--trustAll
Cause 3: Expired Certificate
# Check expiration
openssl s_client -connect ds.example.com:1636 </dev/null 2>/dev/null | \
openssl x509 -noout -dates
# Output:
# notBefore=Jan 1 00:00:00 2025 GMT
# notAfter=Jan 1 00:00:00 2026 GMT ← EXPIRED
Fix: Renew the Certificate
# Generate a new CSR using the existing key
dskeymgr create-certificate-request \
--hostname localhost \
--port 4444 \
--bindDN "uid=admin" \
--bindPassword "password" \
--alias server-cert \
--subject-dn "CN=ds.example.com,O=Example,C=US" \
--output-file ds-csr.pem \
--trustAll
# After getting the signed certificate from your CA, import it
dskeymgr import-certificate \
--hostname localhost \
--port 4444 \
--bindDN "uid=admin" \
--bindPassword "password" \
--certificate-file new-ds-cert.pem \
--alias server-cert \
--trustAll
# Restart DS
bin/stop-ds && bin/start-ds
After renewal, update all clients that had the old certificate imported.
Cause 4: DS Replication Certificate Mismatch
When DS instances replicate, each peer must trust the other’s certificate. After certificate renewal or adding a new node:
# On each DS peer, export the certificate
openssl s_client -connect ds-1.example.com:8989 </dev/null 2>/dev/null | \
openssl x509 -out ds-1-cert.pem
openssl s_client -connect ds-2.example.com:8989 </dev/null 2>/dev/null | \
openssl x509 -out ds-2-cert.pem
# On ds-1: import ds-2's certificate
dskeymgr import-certificate \
--hostname localhost \
--port 4444 \
--bindDN "uid=admin" \
--bindPassword "password" \
--certificate-file ds-2-cert.pem \
--alias ds-2-replication \
--trustAll
# On ds-2: import ds-1's certificate
dskeymgr import-certificate \
--hostname localhost \
--port 4444 \
--bindDN "uid=admin" \
--bindPassword "password" \
--certificate-file ds-1-cert.pem \
--alias ds-1-replication \
--trustAll
Best practice: Use a shared CA certificate instead of self-signed certs. Then each DS instance only needs the CA in its truststore, and certificate renewal doesn’t require updating peers.
Real-World Scenario: DS + RS Co-Located on the Same Host
A common production pattern is running both Directory Server and Replication Server on the same host. When the replication server tries to connect to itself or a peer, you see:
encountered an unexpected error while connecting to replication server
ds-node01.corp.example.com:8989 for domain "cn=schema":
ValidatorException: PKIX path building failed:
sun.security.provider.certpath.SunCertPathBuilderException:
unable to find valid certification path to requested target
encountered an unexpected error while connecting to replication server
ds-node01.corp.example.com:8989 for domain "dc=example,dc=com":
ValidatorException: PKIX path building failed: ...
encountered an unexpected error while connecting to replication server
ds-node01.corp.example.com:8989 for domain "ou=tokens":
ValidatorException: PKIX path building failed: ...
Since PKIX is a TLS-level failure, every replication domain reports the same error — cn=schema, user data (dc=...), tokens (ou=tokens), identities, admin data, and so on. The replication server cannot verify the TLS certificate of the peer (or itself) on port 8989, so no domain can establish a connection. When DS and RS share the same JVM, the replication listener uses the same keystore but may present a different certificate alias than the one trusted by the peer.
Root causes for co-located DS+RS:
Self-signed certificate regenerated during upgrade: When you upgrade ForgeRock DS (e.g., from DS 6.5 to DS 7.x, or DS 7.3 to DS 7.5), the upgrade process may automatically regenerate the self-signed certificate in the keystore. This happens because:
- The new DS version enforces stronger key requirements (e.g., minimum 2048-bit RSA, or SHA-256 signature algorithm instead of SHA-1)
- The
upgradecommand detects the old certificate does not meet the new requirements and silently replaces it - The new certificate has a different fingerprint and public key than the old one
The result: all peer DS instances still trust the old certificate. When the upgraded node tries to replicate, peers reject the new certificate because it is not in their truststore. Every replication domain fails simultaneously because TLS handshake happens before any domain-level protocol exchange.
How to detect this:
# Compare the certificate fingerprint on the upgraded node vs what peers trust # On the UPGRADED node — get the current certificate fingerprint: openssl s_client -connect ds-node01.corp.example.com:8989 </dev/null 2>/dev/null | \ openssl x509 -noout -fingerprint -sha256 # Output: SHA256 Fingerprint=AA:BB:CC:... (NEW fingerprint after upgrade) # On a PEER node — check what certificate it has stored for the upgraded node: dskeymgr list-certificates \ --hostname localhost \ --port 4444 \ --bindDN "uid=admin" \ --bindPassword "password" \ --trustAll # Look for the alias that was imported from ds-node01 — its fingerprint will be DIFFERENT (OLD) # Also check the DS upgrade log for certificate regeneration: grep -i "certificate\|keystore\|regenerat" /path/to/ds/logs/upgrade.logHow to fix — re-exchange certificates after upgrade:
# Step 1: On the UPGRADED node, export the NEW certificate openssl s_client -connect localhost:8989 </dev/null 2>/dev/null | \ openssl x509 -out /tmp/upgraded-node-new-cert.pem # Verify it's the new one: openssl x509 -in /tmp/upgraded-node-new-cert.pem -noout -fingerprint -sha256 -dates # Step 2: On EACH PEER, remove the old trusted certificate and import the new one dskeymgr delete-certificate \ --hostname localhost \ --port 4444 \ --bindDN "uid=admin" \ --bindPassword "password" \ --alias "repl-peer-node01-old" \ --trustAll dskeymgr import-certificate \ --hostname localhost \ --port 4444 \ --bindDN "uid=admin" \ --bindPassword "password" \ --certificate-file /tmp/upgraded-node-new-cert.pem \ --alias "repl-peer-node01" \ --trustAll # Step 3: Restart the peer DS instances to pick up the new truststore bin/stop-ds && bin/start-ds # Step 4: Verify replication recovers dsrepl status \ --hostname localhost \ --port 4444 \ --bindDN "uid=admin" \ --bindPassword "password" \ --trustAllPrevention — upgrade checklist:
- Before upgrade: Export and save the current certificate fingerprint from all nodes
- After upgrade on each node: Compare the new fingerprint — if it changed, immediately re-export and distribute to all peers
- Best long-term fix: Migrate from self-signed to CA-signed certificates (see Option B below). CA-signed certificates survive upgrades because DS trusts the CA, not the individual server certificate
Keystore has multiple certificates and RS picks the wrong one: When the DS keystore contains multiple certificate entries, the replication listener may select a different certificate than what was originally configured. Check which alias the replication listener uses:
# Check which certificate alias the replication server is using
dsconfig get-replication-server-prop \
--hostname localhost \
--port 4444 \
--bindDN "uid=admin" \
--bindPassword "password" \
--provider-name "Multimaster Synchronization" \
--property ssl-cert-nickname \
--trustAll
- Admin connector certificate vs replication certificate: The admin connector (port 4444) and the replication server (port 8989) may use different certificate aliases. If you renewed one but not the other:
# Check admin connector certificate
dsconfig get-administration-connector-prop \
--hostname localhost \
--port 4444 \
--bindDN "uid=admin" \
--bindPassword "password" \
--property ssl-cert-nickname \
--trustAll
# Both should use the same alias, or both certificates must be in each peer's truststore
Diagnosis for co-located DS+RS:
# Step 1: Check what certificate the replication port is presenting
openssl s_client -connect ds-node01.corp.example.com:8989 </dev/null 2>/dev/null | \
openssl x509 -noout -subject -issuer -dates -fingerprint -sha256
# Step 2: Compare with what the LDAPS port presents
openssl s_client -connect ds-node01.corp.example.com:1636 </dev/null 2>/dev/null | \
openssl x509 -noout -subject -issuer -dates -fingerprint -sha256
# If the fingerprints differ, the replication and LDAP listeners use different certificates
# Step 3: List all certificates in the DS keystore
dskeymgr list-certificates \
--hostname localhost \
--port 4444 \
--bindDN "uid=admin" \
--bindPassword "password" \
--trustAll
# Step 4: Check which peers are configured for replication
dsrepl status \
--hostname localhost \
--port 4444 \
--bindDN "uid=admin" \
--bindPassword "password" \
--trustAll
Fix for co-located DS+RS:
# Option A: Re-export and exchange certificates between all peers
# On EACH DS node, export the replication certificate:
openssl s_client -connect localhost:8989 </dev/null 2>/dev/null | \
openssl x509 -out /tmp/local-repl-cert.pem
# On EACH peer, import the other peers' certificates:
dskeymgr import-certificate \
--hostname localhost \
--port 4444 \
--bindDN "uid=admin" \
--bindPassword "password" \
--certificate-file /tmp/peer-repl-cert.pem \
--alias "repl-peer-node02" \
--trustAll
# Option B (recommended): Replace self-signed with CA-signed certificate
# This permanently fixes the trust issue across all peers
dskeymgr create-certificate-request \
--hostname localhost \
--port 4444 \
--bindDN "uid=admin" \
--bindPassword "password" \
--alias server-cert \
--subject-dn "CN=ds-node01.corp.example.com,O=Corp,C=US" \
--subject-alternative-name "DNS:ds-node01.corp.example.com" \
--output-file ds-node01.csr \
--trustAll
# After CA signs it, import with full chain:
dskeymgr import-certificate \
--hostname localhost \
--port 4444 \
--bindDN "uid=admin" \
--bindPassword "password" \
--certificate-file signed-cert.pem \
--ca-certificate-file ca-chain.pem \
--alias server-cert \
--trustAll
# Restart DS — replication will automatically recover
bin/stop-ds && bin/start-ds
All replication domains are affected: The PKIX error is a TLS-level failure — it happens before any LDAP/replication protocol exchange. This means every replication domain will fail with the same error: cn=schema, dc=example,dc=com (user data), cn=tokens (CTS), cn=admin data, and any other configured domains. You will typically see multiple log entries like:
connecting to replication server ds-node01.corp.example.com:8989 for domain "cn=schema": PKIX path building failed...
connecting to replication server ds-node01.corp.example.com:8989 for domain "dc=example,dc=com": PKIX path building failed...
connecting to replication server ds-node01.corp.example.com:8989 for domain "ou=tokens": PKIX path building failed...
connecting to replication server ds-node01.corp.example.com:8989 for domain "ou=identities": PKIX path building failed...
The cn=schema error is often noticed first because DS initializes schema replication before data domains, but the root cause is the same across all domains — the TLS certificate trust is broken at the transport layer. Fixing the certificate once resolves all domains simultaneously.
Cause 5: Wrong Truststore Configured
ForgeRock AM and IDM can use a custom truststore instead of the JVM default:
AM Truststore Configuration
Check $AM_HOME/config/boot.json for custom truststore settings:
{
"stores": {
"trustStore": {
"location": "/path/to/am-truststore.jks",
"password": "changeit"
}
}
}
If a custom truststore is configured, import the DS certificate there — not into the JVM’s cacerts:
keytool -importcert \
-alias forgerock-ds \
-file ds-cert.pem \
-keystore /path/to/am-truststore.jks \
-storepass changeit \
-noprompt
IDM Truststore Configuration
Check $IDM_HOME/conf/system.properties:
javax.net.ssl.trustStore=/path/to/idm-truststore.jks
javax.net.ssl.trustStorePassword=changeit
JVM System Properties
For any Java client, you can set the truststore via JVM arguments:
# Start AM/IDM with explicit truststore
java -Djavax.net.ssl.trustStore=/path/to/truststore.jks \
-Djavax.net.ssl.trustStorePassword=changeit \
-jar application.jar
# Enable TLS debugging to see exactly what's happening
java -Djavax.net.ssl.debug=ssl,handshake \
-jar application.jar
Cause 6: Hostname Mismatch (Related Error)
If the certificate’s CN or SAN doesn’t match the hostname used in the connection:
java.security.cert.CertificateException: No subject alternative names matching
IP address 10.0.0.5 found
Fix: Generate Certificate with Correct SANs
dskeymgr create-certificate-request \
--hostname localhost \
--port 4444 \
--bindDN "uid=admin" \
--bindPassword "password" \
--alias server-cert \
--subject-dn "CN=ds.example.com,O=Example,C=US" \
--subject-alternative-name "DNS:ds.example.com" \
--subject-alternative-name "DNS:ds-0.ds.forgerock.svc.cluster.local" \
--subject-alternative-name "DNS:localhost" \
--subject-alternative-name "IP:10.0.0.5" \
--output-file ds-csr.pem \
--trustAll
Automated Certificate Health Check
This script checks all common certificate issues at once:
#!/bin/bash
# ForgeRock DS Certificate Health Check
# Usage: ./cert-health-check.sh <ds-host> <ldaps-port> [admin-port]
DS_HOST="${1:?Usage: cert-health-check.sh <ds-host> <ldaps-port> [admin-port]}"
DS_PORT="${2:?Usage: cert-health-check.sh <ds-host> <ldaps-port>}"
ADMIN_PORT="${3:-4444}"
ERRORS=0
echo "========================================"
echo "ForgeRock DS Certificate Health Check"
echo "Target: ${DS_HOST}:${DS_PORT}"
echo "========================================"
# Check 1: Can we connect?
echo -e "\n--- Check 1: TLS Connection ---"
CONNECT_OUTPUT=$(openssl s_client -connect "${DS_HOST}:${DS_PORT}" </dev/null 2>&1)
if echo "$CONNECT_OUTPUT" | grep -q "CONNECTED"; then
echo "PASS: TLS connection established"
else
echo "FAIL: Cannot establish TLS connection"
((ERRORS++))
fi
# Check 2: Certificate details
echo -e "\n--- Check 2: Certificate Details ---"
CERT_TEXT=$(echo "$CONNECT_OUTPUT" | openssl x509 -text -noout 2>/dev/null)
if [ -n "$CERT_TEXT" ]; then
SUBJECT=$(echo "$CERT_TEXT" | grep "Subject:" | head -1)
ISSUER=$(echo "$CERT_TEXT" | grep "Issuer:" | head -1)
echo "Subject: ${SUBJECT}"
echo "Issuer: ${ISSUER}"
# Self-signed check
if [ "$SUBJECT" = "$ISSUER" ]; then
echo "WARNING: Certificate is self-signed"
fi
else
echo "FAIL: Cannot read certificate"
((ERRORS++))
fi
# Check 3: Expiration
echo -e "\n--- Check 3: Expiration ---"
DATES=$(echo "$CONNECT_OUTPUT" | openssl x509 -noout -dates 2>/dev/null)
if [ -n "$DATES" ]; then
echo "$DATES"
NOT_AFTER=$(echo "$DATES" | grep "notAfter" | cut -d= -f2)
EXPIRY_EPOCH=$(date -j -f "%b %d %H:%M:%S %Y %Z" "$NOT_AFTER" +%s 2>/dev/null || \
date -d "$NOT_AFTER" +%s 2>/dev/null)
NOW_EPOCH=$(date +%s)
DAYS_LEFT=$(( (EXPIRY_EPOCH - NOW_EPOCH) / 86400 ))
if [ "$DAYS_LEFT" -lt 0 ]; then
echo "FAIL: Certificate EXPIRED ${DAYS_LEFT#-} days ago"
((ERRORS++))
elif [ "$DAYS_LEFT" -lt 30 ]; then
echo "WARNING: Certificate expires in ${DAYS_LEFT} days"
else
echo "PASS: Certificate valid for ${DAYS_LEFT} days"
fi
fi
# Check 4: Chain completeness
echo -e "\n--- Check 4: Certificate Chain ---"
CHAIN_COUNT=$(echo "$CONNECT_OUTPUT" | grep -c "BEGIN CERTIFICATE")
echo "Certificates in chain: ${CHAIN_COUNT}"
if [ "$CHAIN_COUNT" -lt 2 ]; then
echo "WARNING: Chain may be incomplete (no intermediate CA sent)"
fi
VERIFY_RESULT=$(echo "$CONNECT_OUTPUT" | grep "Verify return code")
echo "$VERIFY_RESULT"
if echo "$VERIFY_RESULT" | grep -q "0 (ok)"; then
echo "PASS: Chain verification successful"
else
echo "FAIL: Chain verification failed"
((ERRORS++))
fi
# Check 5: SANs
echo -e "\n--- Check 5: Subject Alternative Names ---"
SANS=$(echo "$CERT_TEXT" | grep -A1 "Subject Alternative Name" | tail -1)
if [ -n "$SANS" ]; then
echo "$SANS"
else
echo "WARNING: No SANs found (only CN-based matching)"
fi
# Check 6: JVM truststore
echo -e "\n--- Check 6: JVM Truststore ---"
if [ -n "$JAVA_HOME" ]; then
TRUSTSTORE="$JAVA_HOME/lib/security/cacerts"
if [ -f "$TRUSTSTORE" ]; then
echo "Truststore: ${TRUSTSTORE}"
DS_IN_TRUST=$(keytool -list -keystore "$TRUSTSTORE" -storepass changeit 2>/dev/null | grep -ci "forgerock\|ds\.")
echo "ForgeRock-related entries: ${DS_IN_TRUST}"
else
echo "WARNING: Default truststore not found at ${TRUSTSTORE}"
fi
else
echo "WARNING: JAVA_HOME not set"
fi
# Summary
echo -e "\n========================================"
echo "Summary: ${ERRORS} error(s) found"
if [ "$ERRORS" -gt 0 ]; then
echo "STATUS: NEEDS ATTENTION"
else
echo "STATUS: HEALTHY"
fi
echo "========================================"
Prevention: Certificate Management Best Practices
1. Use CA-Signed Certificates in Production
Self-signed certificates cause maintenance headaches at scale. Use an internal CA (or cert-manager in Kubernetes) for all DS instances:
# Generate DS key and CSR
dskeymgr create-certificate-request \
--hostname localhost \
--port 4444 \
--bindDN "uid=admin" \
--bindPassword "password" \
--alias server-cert \
--subject-dn "CN=ds.example.com,O=Example,C=US" \
--subject-alternative-name "DNS:ds.example.com" \
--subject-alternative-name "DNS:*.ds.forgerock.svc.cluster.local" \
--key-algorithm RSA \
--key-size 2048 \
--output-file ds-csr.pem \
--trustAll
# Sign with your CA, then import the signed certificate + chain
dskeymgr import-certificate \
--hostname localhost \
--port 4444 \
--bindDN "uid=admin" \
--bindPassword "password" \
--certificate-file signed-ds-cert.pem \
--ca-certificate-file intermediate-ca.pem \
--ca-certificate-file root-ca.pem \
--alias server-cert \
--trustAll
2. Monitor Certificate Expiration
Add certificate expiration to your monitoring system:
# Prometheus-compatible check (returns days until expiry)
EXPIRY=$(openssl s_client -connect ds.example.com:1636 </dev/null 2>/dev/null | \
openssl x509 -noout -enddate 2>/dev/null | cut -d= -f2)
DAYS=$(( ($(date -d "$EXPIRY" +%s) - $(date +%s)) / 86400 ))
echo "forgerock_ds_cert_days_remaining{host=\"ds.example.com\"} $DAYS"
3. Automate Certificate Rotation
For Kubernetes deployments with cert-manager:
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: ds-server-cert
spec:
secretName: ds-tls-secret
duration: 8760h # 1 year
renewBefore: 720h # Auto-renew 30 days before expiry
issuerRef:
name: internal-ca
kind: ClusterIssuer
commonName: ds.forgerock.svc.cluster.local
dnsNames:
- ds.forgerock.svc.cluster.local
- "*.ds.forgerock.svc.cluster.local"
Quick Reference
| Task | Command |
|---|---|
| Check DS certificate | openssl s_client -connect host:1636 </dev/null 2>/dev/null | openssl x509 -text -noout |
| Check expiration | openssl s_client -connect host:1636 </dev/null 2>/dev/null | openssl x509 -noout -dates |
| List DS keystore | dskeymgr list-certificates --hostname host --port 4444 --bindDN uid=admin --bindPassword pass --trustAll |
| Export DS cert | openssl s_client -connect host:1636 </dev/null 2>/dev/null | openssl x509 -out ds.pem |
| Import to JVM | keytool -importcert -alias ds -file ds.pem -keystore $JAVA_HOME/lib/security/cacerts -storepass changeit -noprompt |
| List JVM trust | keytool -list -keystore $JAVA_HOME/lib/security/cacerts -storepass changeit |
| Debug TLS | java -Djavax.net.ssl.debug=ssl,handshake -jar app.jar |
| Generate CSR | dskeymgr create-certificate-request --alias server-cert --subject-dn "CN=ds.example.com" --output-file ds.csr |
