Introduction

CDN SSL certificate errors occur when HTTPS connections fail due to certificate chain issues, domain mismatch, expired certificates, SNI misconfiguration, or TLS protocol mismatches between CDN edge and clients or between CDN and origin. CDNs terminate TLS at edge locations, requiring proper certificate configuration for both client-facing HTTPS and origin-facing connections. Common causes include certificate expired or expiring soon, certificate chain incomplete (missing intermediate CA), domain name mismatch (SAN doesn't cover requested domain), SNI not sent by client or not configured on CDN, origin certificate self-signed and not trusted by CDN, TLS version mismatch (client requires TLS 1.2+, CDN supports only 1.0), certificate revocation (CRL/OCSP failure), custom SSL certificate not uploaded to CDN, shared vs dedicated SSL IP conflicts, and mixed content warnings from HTTPS pages loading HTTP resources. The fix requires understanding TLS termination architecture, certificate chain validation, SNI requirements, and CDN-specific SSL configuration. This guide provides production-proven troubleshooting for CDN SSL issues across Cloudflare, AWS CloudFront, Akamai, Fastly, and generic CDN implementations.

Symptoms

  • Browser shows Your connection is not private warning
  • ERR_SSL_VERSION_OR_CIPHER_MISMATCH in Chrome
  • SSL_ERROR_RX_RECORD_TOO_LONG in Firefox
  • certificate verify failed from CDN to origin
  • CDN returns 502 Bad Gateway with SSL error
  • Mixed content warnings in browser console
  • HTTPS works from some locations but not others
  • Mobile app fails to connect (certificate pinning)
  • TLS handshake fails: ssl3_get_record:wrong version number
  • Certificate shows wrong domain or expired date
  • CDN edge certificate not matching custom domain

Common Causes

  • Custom domain certificate expired or expiring
  • Certificate chain incomplete (missing intermediate)
  • SAN doesn't cover all subdomains being served
  • Origin using self-signed certificate not trusted by CDN
  • SNI required but client doesn't support it (old Android, Java 6)
  • TLS 1.0/1.1 disabled on CDN but required by old clients
  • Origin SSL certificate hostname mismatch
  • CDN origin protocol policy set to HTTP but origin requires HTTPS
  • Certificate uploaded to wrong CDN region/edge
  • DNS CNAME pointing to CDN but SSL not configured
  • HSTS enabled but certificate invalid
  • OCSP stapling misconfigured

Step-by-Step Fix

### 1. Diagnose SSL certificate issues

Check certificate details:

```bash # Check CDN edge certificate echo | openssl s_client -connect cdn.example.com:443 -servername cdn.example.com 2>/dev/null | openssl x509 -noout -dates -subject -issuer

# Output: # notBefore=Jan 1 00:00:00 2026 GMT # notAfter=Mar 31 23:59:59 2026 GMT (EXPIRED if date passed!) # subject=CN = cdn.example.com # issuer=C = US, O = Let's Encrypt, CN = R3

# Check full certificate chain echo | openssl s_client -connect cdn.example.com:443 -servername cdn.example.com -showcerts 2>/dev/null

# Verify certificate chain echo | openssl s_client -connect cdn.example.com:443 -servername cdn.example.com 2>/dev/null | openssl verify -CAfile /etc/ssl/certs/ca-certificates.crt

# Should output: OK # If error: unable to get local issuer certificate

# Check SAN (Subject Alternative Names) echo | openssl s_client -connect cdn.example.com:443 -servername cdn.example.com 2>/dev/null | openssl x509 -noout -ext subjectAltName

# Output: # X509v3 Subject Alternative Name: # DNS:cdn.example.com, DNS:*.example.com

# Check TLS version and cipher echo | openssl s_client -connect cdn.example.com:443 -tls1_2 -servername cdn.example.com 2>&1 | grep -E "Protocol|Cipher"

# Test TLS 1.3 echo | openssl s_client -connect cdn.example.com:443 -tls1_3 -servername cdn.example.com 2>&1 | grep -E "Protocol|Cipher" ```

Browser debugging:

```javascript // Browser console - check security details // Chrome DevTools > Security tab

// Check for mixed content // Chrome: Security > Mixed content // Or console warnings: // Mixed Content: The page at 'https://...' was loaded over HTTPS, // but requested an insecure resource 'http://...'

// Check certificate in browser // Click padlock icon > Certificate is valid // Verify: // - Issued to: cdn.example.com // - Valid from/to dates // - Issued by: trusted CA ```

### 2. Fix Cloudflare SSL configuration

Cloudflare SSL modes:

``` # Cloudflare dashboard > SSL/TLS > Overview

# SSL/TLS Encryption Modes:

# 1. Off - No HTTPS (not recommended) # 2. Flexible - HTTPS to clients, HTTP to origin # - Good for: Origin without SSL certificate # - Warning: Connection not end-to-end encrypted # 3. Full - HTTPS to clients, HTTPS to origin # - Requires: Origin with valid certificate (self-signed OK) # 4. Full (strict) - HTTPS with certificate validation # - Requires: Origin with CA-signed certificate

# Recommended for most sites: Full (strict) ```

Cloudflare Origin CA certificate:

```bash # Cloudflare provides free origin certificates # SSL/TLS > Origin Server > Create Certificate

# Generate certificate: # 1. Choose hostnames: example.com, *.example.com # 2. Choose validity: 15 years (maximum) # 3. Download PEM format

# Install on origin (Nginx): # /etc/nginx/ssl/cloudflare.pem # /etc/nginx/ssl/cloudflare.key

# Nginx configuration: server { listen 443 ssl; server_name example.com;

ssl_certificate /etc/nginx/ssl/cloudflare.pem; ssl_certificate_key /etc/nginx/ssl/cloudflare.key;

# Restrict to Cloudflare IPs (optional but recommended) # allow 173.245.48.0/20; # allow 103.21.244.0/22; # deny all; }

# Cloudflare SSL/TLS settings: # - SSL/TLS Encryption Mode: Full (strict) # - Always Use HTTPS: On # - Minimum TLS Version: 1.2 # - Opportunistic Encryption: On # - TLS 1.3: Enabled ```

Cloudflare custom SSL:

```bash # Upload custom certificate # SSL/TLS > Edge Certificates > Upload Custom Certificate

# Upload: # - Certificate (PEM format) # - Private key # - CA bundle (intermediate certificates)

# Or use Let's Encrypt with Cloudflare: # Using certbot with DNS challenge certbot certonly --dns-cloudflare -d example.com -d *.example.com

# Certificate auto-renews every 90 days # Cloudflare updates edge certificate automatically ```

### 3. Fix CloudFront SSL configuration

CloudFront certificate options:

```bash # CloudFront supports three certificate sources: # 1. CloudFront default certificate (*.cloudfront.net) # 2. ACM certificate (us-east-1 region only) # 3. IAM certificate (legacy, not recommended)

# For custom domains, use ACM:

# Request certificate in us-east-1 (CRITICAL: must be us-east-1) aws acm request-certificate \ --domain-name example.com \ --subject-alternative-names "*.example.com" \ --validation-method DNS \ --region us-east-1

# Validate certificate via DNS aws acm describe-certificate \ --certificate-arn arn:aws:acm:us-east-1:account:certificate/abc-123 \ --region us-east-1

# Add CNAME validation record to DNS # Then wait for validation

# Associate with CloudFront distribution aws cloudfront update-distribution \ --id E123456789 \ --distribution-config file://dist-config.json

# In dist-config.json: # "ViewerCertificate": { # "ACMCertificateArn": "arn:aws:acm:us-east-1:account:certificate/abc-123", # "SSLSupportMethod": "sni-only", # "MinimumProtocolVersion": "TLSv1.2_2021" # } ```

CloudFront origin SSL:

```bash # Origin protocol policy # Distribution > Origins > Edit

# Options: # - HTTP Only: No SSL to origin # - HTTPS Only: Require HTTPS to origin # - Match Viewer: Same as client connection

# Recommended: HTTPS Only with origin certificate

# Origin SSL settings: # - Origin Protocol Policy: HTTPS Only # - Origin SSL Protocols: TLSv1.2, TLSv1.3 # - Origin Read Timeout: 30 seconds

# For S3 origins: # S3 bucket must be accessed via S3 endpoint # CloudFront uses IAM role or OAI for access ```

CloudFront custom SSL errors:

```bash # Error: Certificate not valid for domain # Cause: Domain in request doesn't match certificate SAN

# Fix: Add domain to certificate SAN aws acm request-certificate \ --domain-name example.com \ --subject-alternative-names \ "example.com" \ "www.example.com" \ "cdn.example.com" \ "*.example.com" \ --region us-east-1

# Error: Certificate expired # Cause: ACM certificate not renewed

# Fix: ACM auto-renews if validated # Check validation status: aws acm describe-certificate \ --certificate-arn arn:aws:acm:us-east-1:account:certificate/abc-123 \ --region us-east-1

# Error: SNI not supported # Cause: Old client doesn't send SNI

# Fix: Use dedicated IP (expensive) or accept incompatibility # Most modern clients support SNI ```

### 4. Fix origin certificate issues

Origin certificate requirements:

```bash # CDN to origin connection requires: # 1. Certificate valid for origin hostname # 2. Certificate not expired # 3. Certificate issued by trusted CA (for Full strict mode) # 4. Proper certificate chain

# Self-signed certificate (for Cloudflare Full mode): openssl req -x509 -newkey rsa:4096 \ -keyout /etc/ssl/private/origin.key \ -out /etc/ssl/certs/origin.crt \ -days 3650 \ -nodes \ -subj "/CN=origin.example.com"

# CA-signed certificate (for Full strict mode): # Use Let's Encrypt or commercial CA

# Let's Encrypt with certbot: certbot certonly --standalone -d origin.example.com

# Certificate files: # /etc/letsencrypt/live/origin.example.com/fullchain.pem # /etc/letsencrypt/live/origin.example.com/privkey.pem ```

Nginx origin SSL configuration:

```nginx server { listen 443 ssl http2; server_name origin.example.com;

# Certificate ssl_certificate /etc/letsencrypt/live/origin.example.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/origin.example.com/privkey.pem;

# TLS settings ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256; ssl_prefer_server_ciphers off;

# OCSP Stapling ssl_stapling on; ssl_stapling_verify on; resolver 8.8.8.8 8.8.4.4 valid=300s;

# Restrict to CDN IPs (Cloudflare example) # allow 173.245.48.0/20; # allow 103.21.244.0/22; # allow 104.16.0.0/12; # deny all;

location / { proxy_pass http://backend; } } ```

### 5. Fix SNI configuration

SNI requirements:

```bash # SNI (Server Name Indication) allows multiple SSL certs on one IP # Required for: Most CDN custom SSL certificates # Not supported by: Very old clients (IE6 on XP, Java 6, old Android)

# Check SNI support: # With SNI openssl s_client -connect cdn.example.com:443 -servername cdn.example.com </dev/null 2>/dev/null | openssl x509 -noout -subject

# Without SNI (gets default cert) openssl s_client -connect cdn.example.com:443 </dev/null 2>/dev/null | openssl x509 -noout -subject

# If different certificates, SNI is working ```

Cloudflare SNI:

```bash # Cloudflare supports SNI by default # No configuration needed for most users

# For dedicated IP (if SNI not an option): # Available on Business/Enterprise plans # Contact Cloudflare support

# Check SNI status: # SSL/TLS > Edge Certificates > SNI ```

CloudFront SNI:

```bash # CloudFront default: SNI-only (shared IP) # No additional cost

# For dedicated IP: # - Additional monthly fee per distribution # - Configure in distribution settings

aws cloudfront update-distribution \ --id E123456789 \ --distribution-config '{ "ViewerCertificate": { "ACMCertificateArn": "arn:aws:acm:...", "SSLSupportMethod": "vip", // Dedicated IP "MinimumProtocolVersion": "TLSv1.2_2021" } }' ```

### 6. Fix mixed content warnings

Identify mixed content:

```bash # Browser console shows: # Mixed Content: The page at 'https://example.com' was loaded over HTTPS, # but requested an insecure resource 'http://cdn.example.com/image.png'

# Find all mixed content: # Chrome DevTools > Security > Mixed content

# Or use browser extension: # "Mixed Content Linker" for Chrome

# Crawl site for mixed content: # https://www.whynopadlock.com/ ```

Fix mixed content:

```html <!-- WRONG: Hardcoded HTTP --> <img src="http://cdn.example.com/image.png"> <script src="http://cdn.example.com/app.js"></script>

<!-- CORRECT: Use HTTPS or protocol-relative --> <img src="https://cdn.example.com/image.png"> <script src="https://cdn.example.com/app.js"></script>

<!-- Or protocol-relative (deprecated but works) --> <img src="//cdn.example.com/image.png">

<!-- Best: Use relative paths --> <img src="/assets/image.png"> <script src="/assets/app.js"></script> ```

Content Security Policy for mixed content:

```html <!-- Force HTTPS for all resources --> <meta http-equiv="Content-Security-Policy" content="upgrade-insecure-requests">

<!-- Or via header --> Content-Security-Policy: upgrade-insecure-requests

<!-- Block all mixed content --> Content-Security-Policy: block-all-mixed-content ```

### 7. Fix TLS version and cipher issues

TLS configuration:

```bash # Check supported TLS versions nmap --script ssl-enum-ciphers -p 443 cdn.example.com

# Or use testssl.sh git clone https://github.com/drwetter/testssl.sh cd testssl.sh ./testssl.sh cdn.example.com

# Output shows: # TLS 1.3: Supported # TLS 1.2: Supported # TLS 1.1: Not offered # TLS 1.0: Not offered ```

Cloudflare TLS settings:

``` # Cloudflare dashboard > SSL/TLS > Edge Certificates

# Minimum TLS Version: # - TLS 1.0: Maximum compatibility (not recommended) # - TLS 1.1: Moderate compatibility # - TLS 1.2: Modern security (recommended) # - TLS 1.3: Maximum security (some old client issues)

# Recommended settings: # - Minimum TLS Version: 1.2 # - TLS 1.3: Enabled # - Opportunistic Encryption: On # - Always Use HTTPS: On ```

CloudFront TLS settings:

```bash # Security Policy determines TLS versions # Distribution > General > Edit > Security Policy

# Available policies: # - TLSv1: Legacy (includes TLS 1.0) # - TLSv1.1_2016: Legacy # - TLSv1.2_2018: Modern (recommended) # - TLSv1.2_2021: Most secure

aws cloudfront update-distribution \ --id E123456789 \ --distribution-config '{ "ViewerCertificate": { "MinimumProtocolVersion": "TLSv1.2_2021" } }' ```

### 8. Monitor certificate expiration

Certificate monitoring:

```bash # Check certificate expiry #!/bin/bash check_ssl_expiry() { local host=$1 local days=$(echo | openssl s_client -connect $host:443 -servername $host 2>/dev/null | \ openssl x509 -noout -enddate | \ cut -d= -f2 | \ xargs -I {} date -d "{}" +%s | \ xargs -I {} echo \(( {} - $(date +%s) \) / 86400 | bc)

echo "$host: $days days until expiration"

if [ "$days" -lt 30 ]; then echo "WARNING: Certificate expires in less than 30 days!" return 1 fi }

check_ssl_expiry cdn.example.com check_ssl_expiry origin.example.com ```

Prometheus monitoring:

```yaml # Blackbox exporter for SSL monitoring scrape_configs: - job_name: 'cdn-ssl' metrics_path: /probe params: module: [http_2xx] static_configs: - targets: - https://cdn.example.com - https://origin.example.com relabel_configs: - source_labels: [__address__] target_label: __param_target - target_label: instance replacement: cdn-check - target_label: __address__ replacement: blackbox-exporter:9115

# Grafana alerting rules groups: - name: cdn_ssl rules: - alert: SSLCertExpiringSoon expr: (probe_ssl_earliest_cert_expiry - time()) / 86400 < 30 for: 1h labels: severity: warning annotations: summary: "CDN SSL certificate expiring in less than 30 days"

  • alert: SSLCertExpired
  • expr: (probe_ssl_earliest_cert_expiry - time()) < 0
  • for: 5m
  • labels:
  • severity: critical
  • annotations:
  • summary: "CDN SSL certificate has expired"
  • `

Prevention

  • Use CDN-managed certificates when possible (auto-renewal)
  • Set up certificate expiration alerts (30, 14, 7 days)
  • Configure Full (strict) SSL mode with valid origin certificates
  • Use TLS 1.2+ minimum for modern security
  • Implement CSP upgrade-insecure-requests for mixed content
  • Monitor SSL Labs score for configuration issues
  • Document certificate renewal procedures
  • Test SSL configuration after CDN changes
  • **ERR_SSL_VERSION_OR_CIPHER_MISMATCH**: TLS version or cipher incompatible
  • **ERR_CERT_COMMON_NAME_INVALID**: Domain mismatch in certificate
  • **ERR_CERT_DATE_INVALID**: Certificate expired or not yet valid
  • **ERR_CERT_AUTHORITY_INVALID**: Untrusted certificate authority
  • **Mixed Content warnings**: HTTPS page loading HTTP resources