Back to Guides
OpenSSLCertificates

OpenSSL Verify Certificate Chain: Complete Guide

Learn how to verify SSL/TLS certificate chains with OpenSSL. This guide covers chain validation, debugging missing intermediates, and fixing common trust issues.

12 min readDecember 2025
OpenSSL Chain Verification Guide

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.pem

Common Verification Errors

OpenSSL verify returns numeric error codes. Here are the most common ones:

CodeErrorCommon Cause
0okChain verified successfully
2unable to get issuer certificateMissing intermediate certificate
10certificate has expiredCertificate past validity date
18self signed certificateRoot CA not in trust store
19self signed certificate in chainRoot CA in chain but not trusted
20unable to get local issuer certificateIntermediate/root not in CAfile
21unable to verify the first certificateNo matching issuer found
26unsupported certificate purposeWrong 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 -noout

Error 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):

  1. Server (leaf) certificate
  2. Intermediate CA certificate(s)
  3. 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