Introduction
OpenSSL certificate verification fails when the certificate chain cannot be validated against trusted root certificates. The openssl verify command returns error codes that indicate specific problems - missing intermediates, untrusted roots, expired certificates, or hostname mismatches. Understanding these error codes helps pinpoint and fix certificate configuration issues.
Symptoms
openssl verifyreturns non-zero exit code- Error:
verify error:num=20:unable to get local issuer certificate - Error:
verify error:num=21:unable to verify the first certificate - Error:
verify error:num=27:certificate not trusted - Error:
verify error:num=10:certificate has expired - OpenSSL s_client shows verification errors
- Applications using OpenSSL fail certificate checks
Common Causes
- Missing intermediate certificates in chain
- Root CA not in trust store
- Certificate chain order wrong
- Certificate expired or not yet valid
- Self-signed certificate not trusted
- Hostname doesn't match certificate
- Wrong CA bundle file used
- Trust store outdated or missing roots
Step-by-Step Fix
Step 1: Run Basic Verification
```bash # Verify certificate against default trust store openssl verify server.crt
# Verify with explicit CA bundle openssl verify -CAfile /etc/ssl/certs/ca-bundle.crt server.crt
# Check specific error code openssl verify -verbose server.crt
# Common error codes: # 20: unable to get local issuer certificate (missing intermediate) # 21: unable to verify the first certificate (chain incomplete) # 27: certificate not trusted (root not in trust store) # 10: certificate has expired # 18: self signed certificate # 24: invalid CA certificate ```
Step 2: Check Certificate Chain
```bash # Get certificate chain from server openssl s_client -connect example.com:443 -showcerts 2>/dev/null
# Save each certificate in chain openssl s_client -connect example.com:443 -showcerts 2>/dev/null < /dev/null | \ awk '/BEGIN CERTIFICATE/,/END CERTIFICATE/' > fullchain.pem
# Check chain order openssl verify -CAfile intermediate.crt -untrusted intermediate.crt server.crt ```
Step 3: Fix Missing Intermediate
```bash # If error 20 or 21, intermediate missing
# Find certificate issuer openssl x509 -in server.crt -noout -issuer
# Download intermediate from CA # Check AIA extension for intermediate URL openssl x509 -in server.crt -noout -text | grep -A 4 "Authority Information Access"
# Create full chain cat server.crt intermediate.crt > fullchain.crt
# Verify full chain openssl verify -CAfile /etc/ssl/certs/ca-bundle.crt fullchain.crt ```
Step 4: Fix Trust Store Issues
```bash # If error 27, root CA not trusted
# Check trust store location # Common locations: # /etc/ssl/certs/ca-bundle.crt (many Linux) # /etc/pki/tls/certs/ca-bundle.crt (RHEL/CentOS) # /usr/local/share/certs/ca-root-nss.crt (FreeBSD) # /etc/ssl/cert.pem (Alpine, macOS)
# Check if root exists openssl x509 -in root.crt -noout -subject grep -r "$(openssl x509 -in root.crt -noout -subject)" /etc/ssl/certs/
# Add root to trust store cp root.crt /usr/local/share/ca-certificates/ update-ca-certificates
# Or on RHEL/CentOS cp root.crt /etc/pki/ca-trust/source/anchors/ update-ca-trust ```
Step 5: Verify Certificate Validity
```bash # Check certificate dates openssl x509 -in server.crt -noout -dates
# Check if currently valid openssl x509 -in server.crt -noout -checkend 0
# Returns 0 if valid, 1 if expired or not yet valid
# Check specific time validity openssl x509 -in server.crt -noout -text | grep -A 2 Validity ```
Step 6: Verify Hostname Match
```bash # Check certificate hostname openssl x509 -in server.crt -noout -subject openssl x509 -in server.crt -noout -text | grep -A 1 "Subject Alternative Name"
# Verify hostname explicitly openssl verify -CAfile /etc/ssl/certs/ca-bundle.crt \ -verify_hostname example.com server.crt
# Or with s_client openssl s_client -connect example.com:443 -servername example.com 2>&1 | \ grep "Verify return" ```
Step 7: Verify Chain Order
```bash # Chain must be: server cert, then intermediates, then optionally root # OpenSSL verify needs correct order
# Split and reassemble in correct order # Extract each cert csplit -z fullchain.pem '/-----BEGIN CERTIFICATE-----/' '{*}'
# Verify order: first cert is server, next are intermediates openssl x509 -in xx00 -noout -subject # Should be your server openssl x509 -in xx01 -noout -subject # Should be intermediate
# Reassemble in order cat xx00 xx01 xx02 > ordered-chain.pem ```
Step 8: Comprehensive Verification
```bash # Full verification chain openssl verify -CAfile /etc/ssl/certs/ca-bundle.crt \ -untrusted intermediate.crt \ -verbose server.crt
# Test actual connection openssl s_client -connect example.com:443 \ -servername example.com \ -CAfile /etc/ssl/certs/ca-bundle.crt
# Check return code echo | openssl s_client -connect example.com:443 2>&1 | grep "Verify return" # Should show: Verify return code: 0 (ok) ```
Step 9: Use SSL Labs for External Verification
```bash # SSL Labs provides comprehensive chain analysis # https://www.ssllabs.com/ssltest/analyze.html?d=example.com
# Or use API curl -s "https://api.ssllabs.com/api/v3/analyze?host=example.com&publish=on" | jq ```
Common Pitfalls
- Using wrong CA bundle file location
- Not including intermediate in verification
- Chain order reversed (root before server)
- Trust store missing modern root CAs
- Testing with outdated OpenSSL version
- Not checking hostname in verification
- Self-signed cert treated as normal CA-signed
Best Practices
- Always verify full chain, not just server cert
- Use
-showcertsto capture complete chain from server - Keep trust store updated with latest root CAs
- Document expected chain structure for your certificates
- Verify hostname matching explicitly
- Test with both OpenSSL and SSL Labs
- Use
-verboseflag for detailed error diagnosis
Related Issues
- SSL Certificate Chain Incomplete
- SSL Certificate Not Trusted
- SSL Certificate Expired
- SSL Private Key Mismatch