What's Actually Happening
SSL/TLS certificate validation fails when connecting to HTTPS endpoints. Clients reject the connection due to certificate issues.
The Error You'll See
```bash $ curl https://example.com
curl: (60) SSL certificate problem: unable to get local issuer certificate ```
Browser error:
Your connection is not private
NET::ERR_CERT_AUTHORITY_INVALIDPython error:
ssl.SSLCertVerificationError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failedNode.js error:
Error: unable to verify the first certificateWhy This Happens
- 1.Self-signed certificate - Certificate not signed by trusted CA
- 2.Missing intermediate CA - Certificate chain incomplete
- 3.Expired certificate - Certificate validity period ended
- 4.Domain mismatch - Certificate domain doesn't match requested domain
- 5.Untrusted root CA - Root CA not in trust store
- 6.Clock skew - System time incorrect, outside validity period
Step 1: Check Certificate Details
```bash # Check certificate from server: openssl s_client -connect example.com:443 -servername example.com
# Get certificate: openssl s_client -connect example.com:443 -servername example.com 2>/dev/null | openssl x509 -noout -text
# Check specific details: openssl s_client -connect example.com:443 -servername example.com 2>/dev/null | openssl x509 -noout -dates -subject -issuer
# Check certificate chain: openssl s_client -connect example.com:443 -servername example.com -showcerts
# Check for errors: openssl s_client -connect example.com:443 -servername example.com 2>&1 | grep -i error
# Verify certificate: openssl verify example.com.crt
# Verify with CA bundle: openssl verify -CAfile /etc/ssl/certs/ca-certificates.crt example.com.crt ```
Step 2: Check Certificate Expiration
```bash # Check expiration dates: openssl s_client -connect example.com:443 -servername example.com 2>/dev/null | openssl x509 -noout -dates
# Output: # notBefore=Jan 1 00:00:00 2024 GMT # notAfter=Dec 31 23:59:59 2025 GMT
# Check if expired: openssl s_client -connect example.com:443 -servername example.com 2>/dev/null | openssl x509 -checkend 0
# Returns: # Certificate will expire (or has expired) # OR # Certificate will not expire
# Check days until expiry: openssl s_client -connect example.com:443 -servername example.com 2>/dev/null | openssl x509 -noout -enddate | cut -d= -f2 | xargs -I {} date -d {} +%s | xargs -I {} echo "Expiry timestamp: {}"
# Current time: date +%s
# Compare to see if expired ```
Step 3: Check Certificate Chain
```bash # Check full chain: openssl s_client -connect example.com:443 -servername example.com -showcerts 2>/dev/null
# Count certificates in chain: openssl s_client -connect example.com:443 -servername example.com -showcerts 2>/dev/null | grep -c "BEGIN CERTIFICATE"
# Should be 2+ (leaf + intermediate(s))
# Check chain depth: openssl s_client -connect example.com:443 -servername example.com -showcerts 2>/dev/null | grep -E "depth=[0-9]"
# Verify each certificate: openssl s_client -connect example.com:443 -servername example.com -showcerts 2>/dev/null | sed -n '/BEGIN CERTIFICATE/,/END CERTIFICATE/p' > chain.pem
# Split and verify each: csplit chain.pem '/-----BEGIN CERTIFICATE-----/' '{*}' -f cert- -s for f in cert-*; do [ -s "$f" ] && openssl x509 -in "$f" -noout -subject -issuer done rm cert-* chain.pem ```
Step 4: Check Domain Match
```bash # Check certificate subject: openssl s_client -connect example.com:443 -servername example.com 2>/dev/null | openssl x509 -noout -subject
# Check Subject Alternative Names (SAN): openssl s_client -connect example.com:443 -servername example.com 2>/dev/null | openssl x509 -noout -text | grep -A1 "Subject Alternative Name"
# SAN should include: # DNS:example.com, DNS:www.example.com
# If accessing different domain, certificate won't match
# Test with specific hostname: openssl s_client -connect example.com:443 -servername www.example.com
# Check CN (Common Name): openssl s_client -connect example.com:443 -servername example.com 2>/dev/null | openssl x509 -noout -subject | grep CN
# Modern certificates should use SAN, not just CN ```
Step 5: Check Trust Store
```bash # Check system trust store: ls /etc/ssl/certs/ | head ls /etc/ssl/certs/ca-certificates.crt
# Update CA certificates: # Ubuntu/Debian: update-ca-certificates
# RHEL/CentOS: update-ca-trust
# Check trusted CAs: openssl storeutl -certs /etc/ssl/certs/ca-certificates.crt | grep -E "Subject:|Issuer:" | head -20
# Add custom CA: cp custom-ca.crt /usr/local/share/ca-certificates/ update-ca-certificates
# For Java trust store: keytool -list -keystore $JAVA_HOME/lib/security/cacerts -storepass changeit
# For Python: # Uses system trust store or certifi python -c "import certifi; print(certifi.where())" ```
Step 6: Fix Self-Signed Certificate Issues
```bash # For development with self-signed certificates:
# Option 1: Skip verification (testing only!): curl -k https://example.com
# Python: import ssl import urllib.request context = ssl._create_unverified_context() urllib.request.urlopen('https://example.com', context=context)
# Node.js: process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'; // Not for production!
# Option 2: Add to trust store: # Get self-signed cert: openssl s_client -connect example.com:443 -servername example.com 2>/dev/null | openssl x509 > self-signed.crt
# Add to system trust: sudo cp self-signed.crt /usr/local/share/ca-certificates/self-signed.crt sudo update-ca-certificates
# Python add cert: import certifi import ssl ssl.create_default_context(cafile='/path/to/cert.pem')
# For specific application: export SSL_CERT_FILE=/path/to/cert.pem export REQUESTS_CA_BUNDLE=/path/to/cert.pem ```
Step 7: Fix Intermediate Certificate Issues
```bash # Download intermediate certificate: # From CA issuer URL: openssl s_client -connect example.com:443 -servername example.com 2>/dev/null | openssl x509 -noout -text | grep "CA Issuers - URI" | sed 's/URI://'
# Download: wget -O intermediate.der "http://CA_ISSUER_URL" openssl x509 -inform DER -in intermediate.der -out intermediate.pem
# Create full chain: cat cert.pem intermediate.pem > fullchain.pem
# Configure server with full chain: # Nginx: ssl_certificate /path/to/fullchain.pem;
# Apache: SSLCertificateFile /path/to/cert.pem SSLCertificateChainFile /path/to/intermediate.pem
# HAProxy: bind *:443 ssl crt /path/to/fullchain.pem ```
Step 8: Check System Time
```bash # Check current time: date
# Check against NTP: timedatectl status
# If time wrong, certificate validation fails
# Sync time: timedatectl set-ntp true
# Or: ntpdate pool.ntp.org
# Check timezone: timedatectl
# Set correct timezone: timedatectl set-timezone UTC
# For certificate validation, system time must be: # - After certificate notBefore # - Before certificate notAfter ```
Step 9: Debug Certificate Issues
```bash # Use testssl.sh: git clone https://github.com/drwetter/testssl.sh cd testssl.sh ./testssl.sh https://example.com
# Use SSL Labs: # https://www.ssllabs.com/ssltest/
# Debug with curl: curl -v https://example.com 2>&1 | grep -E "SSL|certificate"
# Debug with OpenSSL: openssl s_client -connect example.com:443 -servername example.com -tlsextdebug -status
# Check OCSP: openssl ocsp -issuer chain.pem -cert cert.pem -url http://ocsp.example.com -resp_text
# Check CRL: openssl crl -in crl.pem -noout -text
# Use online checker: curl https://example.com -v --trace-ascii /tmp/trace.txt cat /tmp/trace.txt ```
Step 10: SSL Certificate Verification Script
```bash # Create verification script: cat << 'EOF' > /usr/local/bin/check-ssl-cert.sh #!/bin/bash
HOST=$1 PORT=${2:-"443"}
echo "=== Certificate Details ===" echo | openssl s_client -connect $HOST:$PORT -servername $HOST 2>/dev/null | openssl x509 -noout -subject -issuer -dates
echo "" echo "=== Certificate Chain ===" echo | openssl s_client -connect $HOST:$PORT -servername $HOST -showcerts 2>/dev/null | awk '/depth=/{print}'
echo "" echo "=== Verification ===" echo | openssl s_client -connect $HOST:$PORT -servername $HOST 2>&1 | grep -E "Verify return|error|alert"
echo "" echo "=== Subject Alternative Names ===" echo | openssl s_client -connect $HOST:$PORT -servername $HOST 2>/dev/null | openssl x509 -noout -text | grep -A1 "Subject Alternative Name"
echo "" echo "=== OCSP Status ===" echo | openssl s_client -connect $HOST:$PORT -servername $HOST -status 2>/dev/null | grep -A5 "OCSP response"
echo "" echo "=== TLS Version ===" echo | openssl s_client -connect $HOST:$PORT -servername $HOST 2>/dev/null | grep "Protocol"
echo "" echo "=== Cipher ===" echo | openssl s_client -connect $HOST:$PORT -servername $HOST 2>/dev/null | grep "Cipher"
echo "" echo "=== Test Connection ===" curl -I https://$HOST 2>&1 | head -5
echo "" echo "=== Check Expiry (days) ===" EXPIRY=$(echo | openssl s_client -connect $HOST:$PORT -servername $HOST 2>/dev/null | openssl x509 -noout -enddate | cut -d= -f2) EXPIRY_EPOCH=$(date -d "$EXPIRY" +%s 2>/dev/null || date -j -f "%b %d %T %Y %Z" "$EXPIRY" +%s) NOW_EPOCH=$(date +%s) DAYS=$(( ($EXPIRY_EPOCH - $NOW_EPOCH) / 86400 )) echo "Days until expiry: $DAYS" if [ $DAYS -lt 30 ]; then echo "WARNING: Certificate expires soon!" fi EOF
chmod +x /usr/local/bin/check-ssl-cert.sh
# Usage: /usr/local/bin/check-ssl-cert.sh example.com 443
# Quick check: alias ssl-check='openssl s_client -connect' ```
SSL Certificate Checklist
| Check | Command | Expected |
|---|---|---|
| Certificate valid | openssl verify | OK |
| Not expired | openssl x509 -dates | Within validity |
| Chain complete | -showcerts | Intermediate present |
| Domain matches | openssl x509 -text | SAN includes domain |
| CA trusted | verify -CAfile | Verify OK |
| System time | date | Correct time |
Verify the Fix
```bash # After fixing certificate validation
# 1. Check certificate openssl s_client -connect example.com:443 | grep "Verify return" // Verify return code: 0 (ok)
# 2. Test connection curl https://example.com // Success, no SSL error
# 3. Check chain openssl s_client -connect example.com:443 -showcerts // Chain complete
# 4. Verify dates openssl s_client -connect example.com:443 | openssl x509 -noout -dates // Valid date range
# 5. Check in browser // Green lock, connection secure
# 6. Test with application python -c "import urllib.request; urllib.request.urlopen('https://example.com')" // No exception ```
Related Issues
- [Fix SSL Certificate Expired](/articles/fix-ssl-certificate-expired)
- [Fix Nginx SSL Certificate Not Trusted](/articles/fix-nginx-ssl-certificate-not-trusted)
- [Fix HTTPS Redirect Loop](/articles/fix-https-redirect-loop)