Quick Answer: Verify a Certificate Chain
# Verify certificate against CA bundle openssl verify -CAfile ca-bundle.crt server.crt # Verify with intermediate certificates openssl verify -CAfile root-ca.crt -untrusted intermediate.crt server.crt # Verify remote server chain openssl s_client -connect example.com:443 -servername example.com < /dev/null 2>&1 | \ grep "Verify return code"
✅ Success: "server.crt: OK" or "Verify return code: 0 (ok)"
Understanding Certificate Chains
A certificate chain (or certificate path) links your server certificate to a trusted root CA through a series of intermediate certificates.
Root CA Certificate
Self-signed, stored in browser/OS trust store
Intermediate CA Certificate(s)
Signed by Root CA, signs server certs
Server (Leaf) Certificate
Your domain certificate, signed by Intermediate
Important: Servers must send the complete chain (leaf + intermediates). Root certificates are NOT sent - clients already have them in their trust store.
Verify Local Certificate Files
Basic Verification
# Verify against system CA certificates openssl verify server.crt # Verify against specific CA bundle openssl verify -CAfile /etc/ssl/certs/ca-certificates.crt server.crt # Verify against your CA file openssl verify -CAfile root-ca.crt server.crt
Verify with Intermediate Certificates
# Using -untrusted for intermediate certificates openssl verify -CAfile root-ca.crt -untrusted intermediate.crt server.crt # Multiple intermediates openssl verify -CAfile root-ca.crt \ -untrusted intermediate1.crt \ -untrusted intermediate2.crt \ server.crt
Verify a Certificate Bundle
# If you have a combined chain file cat intermediate.crt root-ca.crt > ca-chain.crt openssl verify -CAfile ca-chain.crt server.crt # Verify each certificate in the chain individually openssl crl2pkcs7 -nocrl -certfile chain.pem | \ openssl pkcs7 -print_certs -noout
Show Chain Details
# Display chain verification details openssl verify -show_chain -CAfile ca-bundle.crt server.crt # Example output: # server.crt: OK # Chain: # depth=0: CN = example.com (untrusted) # depth=1: CN = DigiCert SHA2 Extended Validation Server CA # depth=2: CN = DigiCert High Assurance EV Root CA
Verify Remote Server Chains
Check Server Certificate Chain
# Basic remote verification openssl s_client -connect example.com:443 -servername example.com < /dev/null 2>&1 | \ grep -E "(Verify return code|depth|verify)" # Successful output: # Verify return code: 0 (ok)
View Full Chain from Server
# Show all certificates sent by server openssl s_client -connect example.com:443 -servername example.com -showcerts < /dev/null 2>&1 # Count certificates in chain openssl s_client -connect example.com:443 -servername example.com -showcerts < /dev/null 2>&1 | \ grep -c "BEGIN CERTIFICATE"
Verify Against Specific CA
# Verify remote server against custom CA bundle openssl s_client -connect internal.example.com:443 \ -servername internal.example.com \ -CAfile /path/to/internal-ca.crt < /dev/null 2>&1 | \ grep "Verify return code" # Verify with system certificates openssl s_client -connect example.com:443 \ -servername example.com \ -CApath /etc/ssl/certs < /dev/null
Extract and Save Chain
# Save complete chain from server
echo | openssl s_client -connect example.com:443 -servername example.com -showcerts 2>/dev/null | \
sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' > chain.pem
# Extract individual certificates
csplit -f cert- chain.pem '/-----BEGIN CERTIFICATE-----/' '{*}' 2>/dev/null
# Verify saved chain
openssl verify -CAfile /etc/ssl/certs/ca-certificates.crt chain.pemCommon Verification Errors
OpenSSL verify returns numeric error codes. Here are the most common ones:
| Code | Error | Common Cause |
|---|---|---|
| 0 | ok | Chain verified successfully |
| 2 | unable to get issuer certificate | Missing intermediate certificate |
| 10 | certificate has expired | Certificate past validity date |
| 18 | self signed certificate | Root CA not in trust store |
| 19 | self signed certificate in chain | Root CA in chain but not trusted |
| 20 | unable to get local issuer certificate | Intermediate/root not in CAfile |
| 21 | unable to verify the first certificate | No matching issuer found |
| 26 | unsupported certificate purpose | Wrong key usage/extended key usage |
Fixing Chain Issues
Error 20/21: Missing Intermediate
error 20 at 0 depth lookup: unable to get local issuer certificate# 1. Find the issuer of your certificate openssl x509 -in server.crt -issuer -noout # 2. Download the intermediate certificate from your CA # (Usually provided with your certificate or on CA website) # 3. Create a chain file (order: leaf → intermediate → root) cat server.crt intermediate.crt > chain.pem # 4. Verify the chain openssl verify -CAfile root-ca.crt chain.pem # 5. Configure your web server with the chain file
Error 10: Certificate Expired
# Check which certificate in chain is expired
for cert in *.crt; do
echo "=== $cert ==="
openssl x509 -in "$cert" -dates -noout
done
# Check if intermediate/root is expired (common issue)
openssl s_client -connect example.com:443 -showcerts 2>/dev/null | \
awk '/BEGIN CERT/,/END CERT/{print}' | \
while read line; do
echo "$line"
done | openssl x509 -dates -nooutError 18/19: Self-Signed Certificate
# Add self-signed root to your trust store # For testing only: openssl verify -CAfile self-signed-root.crt server.crt # For system-wide trust (Ubuntu/Debian): sudo cp self-signed-root.crt /usr/local/share/ca-certificates/ sudo update-ca-certificates # For system-wide trust (RHEL/CentOS): sudo cp self-signed-root.crt /etc/pki/ca-trust/source/anchors/ sudo update-ca-trust
Correct Chain Order
Correct order (Nginx/Apache):
- Server (leaf) certificate
- Intermediate CA certificate(s)
- Root CA (optional, usually not included)
# Create properly ordered chain file cat server.crt intermediate.crt > fullchain.pem # Verify order openssl crl2pkcs7 -nocrl -certfile fullchain.pem | \ openssl pkcs7 -print_certs -text -noout | \ grep "Subject:" # Should show: # Subject: CN=example.com (your server) # Subject: CN=DigiCert... (intermediate) # Subject: CN=DigiCert... (root, if included)
Troubleshooting Guide
Chain works locally but fails on clients
Your server might not be sending the full chain:
# Count certificates sent by server openssl s_client -connect example.com:443 -showcerts 2>/dev/null | \ grep -c "BEGIN CERTIFICATE" # Should be 2+ (server + at least 1 intermediate) # If only 1, you need to configure your server with the chain file
Verification works but browsers show warnings
Check for hostname mismatch or expired intermediates:
# Verify hostname matches
openssl s_client -connect example.com:443 -servername example.com 2>/dev/null | \
openssl x509 -noout -text | grep -A1 "Subject Alternative Name"
# Check all dates in chain
openssl s_client -connect example.com:443 -showcerts 2>/dev/null | \
awk '/BEGIN CERT/,/END CERT/{print}' > /tmp/chain.pem
openssl crl2pkcs7 -nocrl -certfile /tmp/chain.pem | \
openssl pkcs7 -print_certs -text | grep -E "(Subject:|Not After)"Corporate proxy/firewall issues
Some proxies intercept TLS and present their own certificates:
# Check if certificate is from expected issuer openssl s_client -connect example.com:443 2>/dev/null | \ openssl x509 -issuer -noout # If issuer is your company, you're behind a TLS-inspecting proxy # You may need to add the proxy's CA to your trust store
Best Practices Checklist
- Always include intermediate certificates in server config
- Use the correct chain order: leaf → intermediate → root
- Never include the root CA in the chain (clients have it)
- Test chain with multiple browsers and OpenSSL
- Monitor certificate expiration dates (including intermediates)
- Verify chain after any certificate renewal
- Use SSL testing tools (SSL Labs, etc.) for production
- Keep intermediate certificates updated from your CA
Frequently Asked Questions
Why does OpenSSL say "unable to get local issuer certificate"?
This means OpenSSL can't find the issuing CA certificate. Use -CAfile to specify your CA bundle, or -untrusted to provide intermediate certificates.
Should I include the root certificate in my chain?
No. Clients already have root certificates in their trust store. Including it wastes bandwidth and can cause issues with some clients. Only include leaf + intermediates.
What order should certificates be in the chain file?
Start with your server certificate (leaf), then add intermediates in order up to (but not including) the root. Many servers require this specific order.
How do I get intermediate certificates?
Your CA provides them when issuing your certificate. You can also download them from the Authority Information Access (AIA) URL in your certificate.
Why does my chain work in Chrome but fail in OpenSSL?
Browsers may cache intermediates from other sites (AIA fetching). OpenSSL doesn't - it only uses what you provide. Always test with OpenSSL to ensure complete chains.
How do I verify a self-signed certificate?
Use the certificate itself as the CA: openssl verify -CAfile self-signed.crt self-signed.crt. For testing, you can add it to your trust store.
Related Resources
OpenSSL s_client Guide
Test SSL/TLS connections and debug handshakes
Chain Builder Demo
Interactive tool to build and debug certificate chains
CA Hierarchy & Chain of Trust
Understand how certificate chains work
Inspect Certificates
View certificate details and verify key matches
Certificate Anatomy
Understand every field in X.509 certificates
