SSL/TLS and Origin Protection

6 min read Beginner

Cloudflare terminates TLS at the edge and re-encrypts to your origin. The SSL mode setting determines how secure that second hop is. This guide covers the correct configuration for end-to-end encryption.

SSL Modes

Cloudflare offers four SSL modes. Only one is correct for production:

ModeEdge to OriginUse
OffNo encryptionNever
FlexiblePlaintext HTTPNever - origin traffic is unencrypted
FullHTTPS (self-signed OK)Temporary only - does not validate origin cert
Full (Strict)HTTPS (valid cert required)Always - validates origin certificate
Flexible SSL does not protect traffic to your origin. Users see a padlock, but Cloudflare connects to your server over plaintext HTTP. This is the most common Cloudflare misconfiguration.

Set via API

curl -X PATCH "https://api.cloudflare.com/client/v4/zones/${ZONE_ID}/settings/ssl" \
  -H "Authorization: Bearer ${API_TOKEN}" \
  -H "Content-Type: application/json" \
  --data '{"value": "strict"}'

Origin Certificates

Cloudflare Origin Certificates are free, trusted by Cloudflare's edge, and valid for up to 15 years. They replace the need for Let's Encrypt or commercial certificates on your origin.

  1. Generate in Cloudflare dashboard (SSL/TLS > Origin Server)
  2. Choose RSA or ECDSA (ECDSA recommended for performance)
  3. Install the certificate and private key on your origin server
  4. Set SSL mode to Full (Strict)
# Verify origin certificate is installed
openssl s_client -connect origin.example.com:443 -servername example.com < /dev/null 2>&1 \
  | openssl x509 -noout -issuer -dates

# Expected issuer: Cloudflare Origin SSL Certificate Authority

HSTS Configuration

HTTP Strict Transport Security tells browsers to always use HTTPS. Once enabled with preload, it cannot be easily reversed.

Recommended settings

Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
  • max-age=31536000 - 1 year. Required minimum for preload lists.
  • includeSubDomains - applies to all subdomains. Ensure all subdomains support HTTPS before enabling.
  • preload - submit to browser preload lists. Effectively permanent.

Enable via API

curl -X PATCH "https://api.cloudflare.com/client/v4/zones/${ZONE_ID}/settings/security_header" \
  -H "Authorization: Bearer ${API_TOKEN}" \
  -H "Content-Type: application/json" \
  --data '{"value": {"strict_transport_security": {"enabled": true, "max_age": 31536000, "include_subdomains": true, "preload": true}}}'
Preload is permanent. Once your domain is on browser preload lists, removing HSTS requires a separate removal request and takes months. Only enable preload when you are certain all subdomains support HTTPS.

Authenticated Origin Pulls

Authenticated Origin Pulls (AOP) adds mutual TLS between Cloudflare and your origin. Your origin only accepts connections presenting Cloudflare's client certificate.

  1. Download Cloudflare's origin pull CA certificate
  2. Configure your origin web server to require client certificates signed by this CA
  3. Enable AOP in Cloudflare dashboard or API
# Enable Authenticated Origin Pulls
curl -X PATCH "https://api.cloudflare.com/client/v4/zones/${ZONE_ID}/settings/tls_client_auth" \
  -H "Authorization: Bearer ${API_TOKEN}" \
  -H "Content-Type: application/json" \
  --data '{"value": "on"}'

This ensures your origin only accepts traffic from Cloudflare. Direct connections to your origin IP will be rejected because they don't present the client certificate.

TLS Version Enforcement

Set minimum TLS version to 1.2. TLS 1.0 and 1.1 are deprecated and have known vulnerabilities.

# Set minimum TLS version
curl -X PATCH "https://api.cloudflare.com/client/v4/zones/${ZONE_ID}/settings/min_tls_version" \
  -H "Authorization: Bearer ${API_TOKEN}" \
  -H "Content-Type: application/json" \
  --data '{"value": "1.2"}'

TLS 1.3 is enabled by default on Cloudflare and should stay enabled. It provides better performance (fewer round trips) and stronger security.

Verification

# Check SSL mode (should be "strict")
curl -s "https://api.cloudflare.com/client/v4/zones/${ZONE_ID}/settings/ssl" \
  -H "Authorization: Bearer ${API_TOKEN}" | jq '.result.value'

# Check HSTS header
curl -sI https://example.com | grep -i strict-transport

# Check TLS version
openssl s_client -connect example.com:443 -tls1_2 < /dev/null 2>&1 | grep "Protocol"

# Check minimum TLS (should reject 1.1)
openssl s_client -connect example.com:443 -tls1_1 < /dev/null 2>&1 | grep "handshake"
# Expected: handshake failure
Full TLS posture review? See our Cloudflare Everywhere Security engagement for comprehensive TLS hardening with evidence packs.