SSL/TLS and Origin Protection
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:
| Mode | Edge to Origin | Use |
|---|---|---|
| Off | No encryption | Never |
| Flexible | Plaintext HTTP | Never - origin traffic is unencrypted |
| Full | HTTPS (self-signed OK) | Temporary only - does not validate origin cert |
| Full (Strict) | HTTPS (valid cert required) | Always - validates origin certificate |
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.
- Generate in Cloudflare dashboard (SSL/TLS > Origin Server)
- Choose RSA or ECDSA (ECDSA recommended for performance)
- Install the certificate and private key on your origin server
- 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}}}' 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.
- Download Cloudflare's origin pull CA certificate
- Configure your origin web server to require client certificates signed by this CA
- 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