DEV Community

Sohana Akbar
Sohana Akbar

Posted on

Ingress with CloudFlare + cert-manager: A Frontend-Friendly Tutorial

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"
Enter fullscreen mode Exit fullscreen mode

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)