When you visit a website over HTTPS, your browser doesn't just check the server's certificate — it verifies an entire chain of trust from the server certificate all the way up to a root Certificate Authority (CA) embedded in your operating system. Getting this chain wrong is one of the most common causes of SSL errors in production.
A certificate chain (or chain of trust) is a sequence of certificates where each certificate is signed by the next one in the chain:
Your Server Certificate (end-entity)
↑ signed by
Intermediate CA Certificate
↑ signed by
Root CA Certificate (self-signed, trusted by OS/browser)
The chain can have multiple intermediates:
End-Entity → Intermediate 1 → Intermediate 2 → Root CA
Root CA private keys are the crown jewels of the PKI system. If a root key is compromised, every certificate it ever signed becomes untrusted. By delegating to intermediates:
When your browser receives a server certificate, it performs these checks:
If any step fails, you get an SSL error.
Most certificates include an Authority Information Access (AIA) extension that tells clients where to download the issuer's certificate:
Authority Information Access:
CA Issuers - URI:http://crt.rootca1.amazontrust.com/rootca1.cer
OCSP - URI:http://ocsp.rootca1.amazontrust.com
The CA Issuers URL points to the issuer's certificate in DER or PKCS#7 format. Browsers and tools can follow these URLs to fetch missing intermediates automatically.
1. Start with end-entity certificate
2. Extract AIA CA Issuers URL → http://crt.example.com/intermediate.cer
3. Fetch intermediate certificate
4. Extract AIA from intermediate → http://crt.example.com/root.cer
5. Fetch root certificate (or find it in trust store)
6. Chain complete: end-entity → intermediate → root
Tip: The Chain Builder tab in our SSL Certificate Decoder automates this process — paste your server cert and it follows AIA URLs to build the full chain.
openssl s_client -connect rawops.dev:443 -showcerts
This shows all certificates the server sends. Look for the ---BEGIN CERTIFICATE--- blocks.
# Save the full chain from a live server
openssl s_client -connect rawops.dev:443 -showcerts 2>/dev/null | \
awk '/BEGIN CERT/,/END CERT/{ print }' > fullchain.pem
# Basic verification against system trust store
openssl verify server.pem
# Verify with explicit intermediate
openssl verify -untrusted intermediate.pem server.pem
# Verify with explicit root + intermediate
openssl verify -CAfile root.pem -untrusted intermediate.pem server.pem
# Split a chain file into individual certificates
csplit -z -f cert- fullchain.pem '/-----BEGIN CERTIFICATE-----/' '{*}'
# Inspect each one
for f in cert-*; do
echo "=== $f ==="
openssl x509 -in "$f" -noout -subject -issuer -dates
done
Symptom: Works in Chrome (desktop), fails on mobile or curl.
Chrome can use AIA to fetch missing intermediates. Mobile browsers and curl often cannot.
# This will fail if intermediates are missing
curl https://example.com
# curl: (60) SSL certificate problem: unable to get local issuer certificate
# Check what the server actually sends
openssl s_client -connect example.com:443 2>/dev/null | grep -c "BEGIN CERTIFICATE"
# Should be 2+ (server cert + intermediates). If 1, intermediates are missing.
Fix: Configure your server to send the full chain:
# Nginx — use fullchain.pem, not just cert.pem
ssl_certificate /etc/ssl/fullchain.pem;
The correct order in a chain file is:
1. End-entity certificate (your server cert)
2. Intermediate CA certificate(s)
3. Root CA certificate (optional — usually not needed)
If the order is wrong, some clients will reject the chain.
# Concatenate in the correct order
cat server.crt intermediate.crt > fullchain.pem
An expired intermediate breaks the entire chain, even if your server cert is valid.
# Check all certificates in a chain for expiry
openssl crl2pkcs7 -nocrl -certfile fullchain.pem | \
openssl pkcs7 -print_certs -noout -text | \
grep -A2 "Validity"
Some CAs have certificates signed by multiple roots (cross-signing). This can create ambiguous chains. Let's Encrypt famously had cross-signed chains that caused issues on older Android devices.
Let's Encrypt R3 (signed by DST Root CA X3 — expired Sept 2021)
Let's Encrypt R3 (signed by ISRG Root X1 — current)
Modern systems use the ISRG Root X1 path. Older devices that don't have ISRG Root X1 in their trust store need the cross-signed chain.
Certbot handles this automatically:
# fullchain.pem is already in the right order
ls /etc/letsencrypt/live/yourdomain.com/
# cert.pem — end-entity only
# chain.pem — intermediates only
# fullchain.pem — cert + chain (USE THIS)
# privkey.pem — private key
Most CAs provide separate files. You need to concatenate them:
# Typical files from a CA
# - your_domain.crt (server certificate)
# - intermediate.crt (or ca-bundle.crt)
cat your_domain.crt intermediate.crt > fullchain.pem
Extract the chain from a PKCS#12 file:
# Extract certificate and chain
openssl pkcs12 -in cert.pfx -nokeys -out fullchain.pem
# Extract private key separately
openssl pkcs12 -in cert.pfx -nocerts -nodes -out key.pem
Or use the PFX Decoder tool to decode it visually and copy the chain.
After configuring your server, verify the chain:
# Quick check — should show "Verify return code: 0 (ok)"
echo | openssl s_client -connect yourdomain.com:443 2>/dev/null | grep "Verify return code"
# Full check with chain details
openssl s_client -connect yourdomain.com:443 -servername yourdomain.com
# Test with curl (strict about chains)
curl -I https://yourdomain.com
# Online tool: SSL Labs
# https://www.ssllabs.com/ssltest/
Certificate chains link your server's identity to a trusted root CA. The most common issues — missing intermediates, wrong order, expired CAs — are all detectable and fixable. Key takeaways:
openssl s_client -showcerts to see what your server actually sends