TL;DR: You’ve built an awesome frontend app. You have a Kubernetes cluster. But setting up HTTPS with Cloudflare proxy and automatic SSL certificates feels like backend black magic. Let’s fix that — no deep network engineering degree required.
By the end of this, you’ll have:
A Kubernetes Ingress routing traffic to your frontend service.
Cloudflare as your DNS + proxy (your domain, e.g., app.example.com).
cert-manager issuing free, auto-renewing Let’s Encrypt certificates.
Full HTTPS + Cloudflare’s CDN/caching benefits.
🧠 Mental model (the "what & why")
Component Job description (in plain English)
Ingress The "traffic cop" that says: "Requests for app.example.com go to my frontend service"
Cloudflare The "front desk": hides your real server IP, provides SSL between user and Cloudflare, caches static assets
cert-manager The "notary": gets trusted SSL certificates from Let's Encrypt so browsers trust your site
Origin CA certificate A special cert Cloudflare gives you for the encrypted tunnel between Cloudflare and your Kubernetes cluster
⚠️ Important: We'll use Cloudflare's Origin CA certificate (trusted only by Cloudflare) + Let's Encrypt via cert-manager for actual user-facing TLS. This ensures full end-to-end encryption.
📋 Prerequisites (don't skip this)
A domain name managed in Cloudflare (e.g., example.com)
A Kubernetes cluster (local: minikube/k3d, cloud: EKS/GKE/AKS, or even Docker Desktop)
kubectl configured to talk to your cluster
helm installed (we'll use it to install cert-manager)
Your frontend app deployed as a Kubernetes Service (type: ClusterIP)
If you don’t have a frontend service yet, here’s a minimal example to test with:
yaml
frontend-service.yaml
apiVersion: v1
kind: Service
metadata:
name: my-frontend-svc
spec:
selector:
app: my-frontend
ports:
- port: 80
targetPort: 80
🚀 Step 1: Prepare Cloudflare — get the Origin Certificate
We need a certificate that Cloudflare will trust when talking to your cluster.
Log into Cloudflare → your domain → SSL/TLS → Origin Server.
Click Create Certificate.
Leave defaults (Let Cloudflare generate a private key + CSR).
In Hostnames, add:
*.example.com (if you have multiple subdomains)
app.example.com (your specific frontend domain)
Choose RSA (universal compatibility) → Create.
You’ll see two blocks:
Origin Certificate (long PEM block)
Private Key (starts with -----BEGIN PRIVATE KEY-----)
Save both as text files somewhere safe.
👉 These are your cluster’s credentials to prove identity to Cloudflare.
🔐 Step 2: Store the cert + key as a Kubernetes Secret
Create a Kubernetes Secret containing the Cloudflare Origin certificate.
bash
kubectl create secret tls cloudflare-origin-cert \
--cert=origin_cert.pem \
--key=origin_private_key.pem \
--namespace default
Check it exists:
bash
kubectl get secret cloudflare-origin-cert
🔍 The name (cloudflare-origin-cert) is arbitrary — pick something you’ll remember.
🌐 Step 3: Deploy an Ingress Controller (if you don’t have one)
We need an actual Ingress controller (like NGINX or Traefik). Let’s use NGINX Ingress — it’s widely used and well-documented.
bash
helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
helm repo update
helm install ingress-nginx ingress-nginx/ingress-nginx \
--set controller.publishService.enabled=true
Wait for the external IP (or hostname if using minikube tunnel):
bash
kubectl get svc ingress-nginx-controller
Copy the EXTERNAL-IP or hostname. You’ll need it for DNS.
🧭 Step 4: Point Cloudflare DNS to your cluster
We have to tell Cloudflare: “Hey, requests for app.example.com should go to my Ingress controller’s IP.”
In Cloudflare Dashboard → your domain → DNS → Add record:
Type: A (or CNAME if you have a hostname)
Name: app
IPv4 address: your EXTERNAL-IP from step 3
Proxy status: ✅ Proxied (orange cloud) — this is crucial!
Save.
Wait a few minutes for DNS propagation (or test with dig app.example.com).
🧩 Step 5: Install cert-manager (automatic SSL wizard)
cert-manager will get Let’s Encrypt certificates for your domain and renew them automatically.
bash
helm repo add jetstack https://charts.jetstack.io
helm repo update
helm install cert-manager jetstack/cert-manager \
--namespace cert-manager \
--create-namespace \
--set installCRDs=true
Wait until all pods are running:
bash
kubectl get pods -n cert-manager
🌍 Step 6: Create a ClusterIssuer (Let's Encrypt "boss")
A ClusterIssuer tells cert-manager which CA to use. We'll use Let's Encrypt Production (no staging for real apps).
yaml
cluster-issuer.yaml
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-prod
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
email: your-email@example.com # <-- change this
privateKeySecretRef:
name: letsencrypt-prod
solvers:
- http01:
ingress:
class: nginx
Apply it:
bash
kubectl apply -f cluster-issuer.yaml
Check status:
bash
kubectl get clusterissuer letsencrypt-prod
Should show READY=True
✨ Step 7: Write the Ingress YAML (the "frontend-friendly" part)
Here’s the magic — one file that ties everything together.
yaml
ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: frontend-ingress
annotations:
# Tell NGINX to trust the Cloudflare Origin cert
nginx.ingress.kubernetes.io/auth-tls-secret: "default/cloudflare-origin-cert"
nginx.ingress.kubernetes.io/auth-tls-verify-client: "on"
nginx.ingress.kubernetes.io/auth-tls-verify-depth: "1"
nginx.ingress.kubernetes.io/auth-tls-pass-certificate-to-upstream: "true"
# cert-manager annotations: get a Let's Encrypt cert for this domain
cert-manager.io/cluster-issuer: "letsencrypt-prod"
# Optional: force HTTPS redirect
nginx.ingress.kubernetes.io/ssl-redirect: "true"
spec:
ingressClassName: nginx
tls:
- hosts:
- app.example.com # <-- your actual domain
secretName: frontend-tls # cert-manager will create this secret
rules:
- host: app.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: my-frontend-svc
port:
number: 80
Deploy it:
bash
kubectl apply -f ingress.yaml
Wait ~30–60 seconds for cert-manager to do its job.
Check certificate status:
bash
kubectl get certificate
kubectl describe certificate frontend-tls
Once READY=True, you're golden.
🧪 Step 8: Test it!
Open your browser → https://app.example.com
✅ Green lock icon (Let's Encrypt cert)
✅ Cloudflare logo in the network tab
✅ Your frontend loads
Check the chain:
Browser → Cloudflare (HTTPS) → your cluster (Cloudflare Origin cert) → your frontend pod
🛠️ Troubleshooting (common frontend-person problems)
Problem Most likely fix
ERR_SSL_PROTOCOL_ERROR Cloudflare SSL/TLS setting must be Full (strict)
Certificate stuck in Issuing Wrong email or http01 solver can't reach Ingress
502 Bad Gateway from Cloudflare Your Ingress controller is unreachable — check DNS points to the correct external IP
Cloudflare Origin cert not recognized Wrong secret name in auth-tls-secret annotation
cert-manager says challenge pending Your Ingress controller needs to be reachable from the internet (Let's Encrypt calls back to validate)
🔧 Cloudflare SSL/TLS setting:
Go to Cloudflare → SSL/TLS → Full (strict) — not Flexible, not Off.
🧠 Why this approach is great for frontend developers
No manual cert renewal — cert-manager handles it.
Cloudflare CDN — your static assets (JS, CSS, images) fly fast.
No exposing your cluster IP — Cloudflare proxies everything.
Easy to add more frontends — just add more host rules in Ingress.
Works with SPAs — you can add custom nginx.ingress.kubernetes.io/rewrite-target if needed.
📦 Bonus: Deploy a frontend + this setup in one script (for testing)
bash
!/bin/bash
deploy-frontend.sh
kubectl create deployment my-frontend --image=nginx --port=80
kubectl expose deployment my-frontend --port=80 --target-port=80 --name=my-frontend-svc
kubectl apply -f cluster-issuer.yaml
kubectl apply -f ingress.yaml
echo "Done! Wait 1 min then visit https://app.example.com"
🧹 Cleanup
bash
kubectl delete ingress frontend-ingress
kubectl delete clusterissuer letsencrypt-prod
helm uninstall cert-manager -n cert-manager
kubectl delete secret cloudflare-origin-cert
🎯 Final checklist
Cloudflare DNS has app.example.com proxied (orange cloud)
Cloudflare SSL/TLS mode is Full (strict)
Origin certificate + key saved as Kubernetes secret
cert-manager installed
ClusterIssuer shows READY=True
Ingress uses both cert-manager.io/cluster-issuer and Cloudflare auth-tls annotations
kubectl get certificate shows READY=True
When all of these are ✅, you’ve built a production-grade HTTPS setup that would make your frontend — and your users — very happy.
Found this helpful?
Follow me on dev.to for more frontend-friendly Kubernetes and cloud native tutorials.
Got stuck? Drop a comment — I read every one.
Top comments (0)