async provision(): Promise<void> {
const cmd = certbot certonly --webroot -w ${this.config.webroot} \ --email ${this.config.email} --agree-tos --non-interactive \ -d ${this.config.domain};
try {
execSync(cmd, { stdio: 'inherit' });
console.log(`Certificate provisioned for ${this.config.domain}`);
} catch (error) {
throw new Error(`Certbot failed: ${error}`);
}
}
verifyChain(): boolean {
const certPath = /etc/letsencrypt/live/${this.config.domain}/fullchain.pem;
if (!existsSync(certPath)) return false;
const cert = readFileSync(certPath, 'utf-8');
return cert.includes('BEGIN CERTIFICATE') && cert.includes('END CERTIFICATE');
}
}
**Private Origins (Cloudflare Origin CA)**
If your origin sits behind a firewall, uses private IPs, or blocks public DNS validation, Cloudflare's Origin CA provides a dedicated certificate authority. These certificates are valid for up to 15 years and are trusted exclusively by Cloudflare's edge network.
```typescript
// origin-ca-manager.ts
import fetch from 'node-fetch';
interface OriginCARequest {
zoneId: string;
hostnames: string[];
requestType: 'origin-rsa' | 'origin-ecc';
validity: number; // days
}
export class CloudflareOriginCA {
constructor(private apiToken: string) {}
async generateCertificate(req: OriginCARequest): Promise<{ cert: string; key: string }> {
const response = await fetch('https://api.cloudflare.com/client/v4/zones/' + req.zoneId + '/origin_tls_certs', {
method: 'POST',
headers: {
'Authorization': `Bearer ${this.apiToken}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
hostnames: req.hostnames,
request_type: req.requestType,
validity: req.validity
})
});
const data = await response.json();
if (!data.success) throw new Error(data.errors[0].message);
return {
cert: data.result.certificate,
key: data.result.private_key
};
}
}
Step 2: Enforce TLS at the Web Server
The origin web server must terminate TLS correctly and reject plaintext HTTP requests. Below is a hardened Nginx configuration that enforces modern cipher suites, enables OCSP stapling, and binds to the provisioned certificate.
# /etc/nginx/sites-available/origin-secure.conf
server {
listen 443 ssl http2;
server_name api.yourdomain.com;
ssl_certificate /etc/ssl/origin/fullchain.pem;
ssl_certificate_key /etc/ssl/origin/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers on;
ssl_session_timeout 1d;
ssl_session_cache shared:SSL:10m;
# OCSP Stapling for certificate validation
ssl_stapling on;
ssl_stapling_verify on;
resolver 1.1.1.1 1.0.0.1 valid=300s;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
# Explicitly drop HTTP traffic
server {
listen 80;
server_name api.yourdomain.com;
return 444; # Nginx-specific: close connection without response
}
Navigate to the Cloudflare dashboard β SSL/TLS β Overview. Switch the encryption mode from Flexible to Full (strict). This forces Cloudflare to validate the origin certificate against a trusted CA chain before forwarding requests.
Enable Always Use HTTPS under SSL/TLS β Edge Certificates. Without this toggle, clients can initiate connections over http://, and Cloudflare will forward those plaintext requests to the origin until a redirect occurs. Enforcing HTTPS at the edge eliminates protocol downgrade vectors.
Step 4: Validate the Pipeline
External DNS resolution will route you to Cloudflare's edge. To verify the origin is actually serving TLS, bypass DNS and connect directly to the origin IP.
// pipeline-validator.ts
import https from 'https';
import { Agent } from 'https';
interface ValidationParams {
hostname: string;
originIp: string;
port: number;
}
export async function validateOriginTLS(params: ValidationParams): Promise<boolean> {
return new Promise((resolve, reject) => {
const agent = new Agent({
rejectUnauthorized: true,
servername: params.hostname // SNI validation
});
const req = https.request({
hostname: params.originIp,
port: params.port,
path: '/',
method: 'HEAD',
agent: agent,
headers: { Host: params.hostname }
}, (res) => {
resolve(res.statusCode === 200);
});
req.on('error', (err) => {
console.error(`Origin TLS validation failed: ${err.message}`);
resolve(false);
});
req.end();
});
}
Architecture Decisions & Rationale
- Full (Strict) over Full: Full mode accepts self-signed certificates, which means an attacker with network access could inject a fraudulent certificate and intercept traffic. Full (Strict) requires a CA-signed chain, enabling cryptographic authentication.
- Origin CA for Private Networks: Public CAs require DNS or HTTP validation, which fails for internal origins. Origin CA solves this by trusting Cloudflare's edge network exclusively, eliminating validation bottlenecks.
- Port 80 Rejection: Returning
444 (connection drop) instead of a 301 redirect prevents information leakage and reduces attack surface. Firewalls should also block inbound port 80 at the network layer.
- SNI Enforcement: The
servername parameter in the validation script ensures the origin presents the correct certificate when hosting multiple domains on a single IP. This prevents certificate mismatch errors in multi-tenant environments.
Pitfall Guide
1. The Redirect Spiral
Explanation: When Flexible mode is active, Cloudflare sends HTTP to the origin. If the origin application (e.g., WordPress, Laravel) enforces HTTPS via framework-level redirects, it responds with a 301 to https://. Cloudflare receives the redirect, resolves it to itself, and sends another HTTP request. The cycle repeats until the browser throws a "too many redirects" error.
Fix: Disable application-level HTTPS enforcement while on Flexible. Migrate to Full (Strict) immediately, allowing the web server to handle TLS termination natively.
2. Certificate Chain Fragmentation
Explanation: Origins often install only the leaf certificate (cert.pem) instead of the full chain (fullchain.pem). Cloudflare's validator rejects the connection because intermediate certificates are missing, causing 525 SSL handshake failed errors.
Fix: Always deploy the concatenated chain file. Verify with openssl s_client -connect origin_ip:443 -servername yourdomain.com and confirm the chain depth matches the CA's documentation.
3. SNI Routing Failures
Explanation: Modern origins host multiple applications on a single IP address. If the web server lacks proper Server Name Indication (SNI) configuration, it defaults to the first virtual host, returning mismatched certificates or routing traffic to the wrong backend.
Fix: Ensure server_name directives in Nginx/Apache match the exact hostname. Test with openssl s_client -servername api.yourdomain.com -connect ip:443 to verify correct virtual host selection.
4. Origin CA Trust Scope Confusion
Explanation: Cloudflare Origin CA certificates are only trusted by Cloudflare's edge network. If you accidentally route traffic through another CDN, load balancer, or direct IP access, browsers and external tools will reject the certificate as untrusted.
Fix: Reserve Origin CA exclusively for Cloudflare-protected origins. Use public CAs (Let's Encrypt, DigiCert) for any infrastructure that must be accessible outside Cloudflare's network.
5. HSTS Preload Conflicts
Explanation: Enabling HTTP Strict Transport Security (HSTS) with preload instructs browsers to permanently enforce HTTPS. If you later downgrade to Flexible mode or misconfigure the origin, cached HSTS policies will block access until the browser cache expires or is manually cleared.
Fix: Deploy HSTS with a short max-age (e.g., 300) during testing. Only enable preload after verifying Full (Strict) stability across all environments.
6. Firewall Port Exposure
Explanation: Leaving port 80 open at the network firewall level allows direct HTTP access to the origin, bypassing Cloudflare entirely. Attackers can scan the origin IP, exploit unpatched services, or inject malicious payloads directly.
Fix: Restrict inbound traffic to Cloudflare's IP ranges only. Use iptables or cloud security groups to drop all non-Cloudflare traffic on ports 80 and 443.
Production Bundle
Action Checklist
Decision Matrix
| Scenario | Recommended Approach | Why | Cost Impact |
|---|
| Public SaaS with direct user access | Let's Encrypt + Full (Strict) | Public validation works; standard CA trust required | Free (Let's Encrypt) + CDN egress |
| Internal tool behind VPC/firewall | Cloudflare Origin CA + Full (Strict) | Bypasses public DNS validation; trusted by edge | Free (Origin CA) + minimal compute |
| Legacy monolith with mixed protocols | Full (Strict) + gradual migration | Enables TLS without refactoring app logic | Low (config changes only) |
| PCI-DSS / HIPAA compliance workload | Full (Strict) + HSTS + OCSP Stapling | Meets cryptographic authentication and audit requirements | Medium (cert management overhead) |
Configuration Template
# cloudflare-ssl-config.tf
resource "cloudflare_zone_settings_override" "strict_ssl" {
zone_id = var.cloudflare_zone_id
settings {
ssl = "strict"
always_use_https = "on"
min_tls_version = "1.2"
tls_1_3 = "on"
automatic_https_rewrites = "on"
}
}
resource "cloudflare_record" "origin_dns" {
zone_id = var.cloudflare_zone_id
name = "api"
value = var.origin_ip
type = "A"
proxied = true
}
Quick Start Guide
- Identify your origin accessibility: Publicly routable? Use Let's Encrypt. Behind a firewall? Generate a Cloudflare Origin CA certificate.
- Install the certificate on your web server: Place
fullchain.pem and privkey.pem in /etc/ssl/origin/. Update Nginx/Apache to listen on 443 ssl and reject port 80.
- Update Cloudflare settings: Navigate to SSL/TLS β Overview. Switch to Full (strict). Enable Always Use HTTPS under Edge Certificates.
- Validate the pipeline: Run the TypeScript validation script or
curl -I --resolve yourdomain.com:443:<origin_ip> https://yourdomain.com/. Confirm 200 OK and valid certificate chain.
- Monitor and automate: Schedule weekly TLS checks. Configure certbot cron jobs or Terraform pipelines to handle renewals. Document the architecture for compliance audits.
The browser padlock is a frontend indicator, not an architectural guarantee. Securing the edge-to-origin leg requires deliberate certificate provisioning, strict validation policies, and network-level access controls. Implementing Full (strict) mode transforms your CDN from a traffic proxy into a verifiable encryption boundary, closing the plaintext gap that Flexible SSL leaves wide open.