In this lab, we built a production-grade HTTPS setup for applications running on:
- Azure Kubernetes Service
- Azure DNS
- NGINX Ingress Controller
- cert-manager
- Let's Encrypt
We exposed:
hello.az.innopy.devapi.az.innopy.dev
And secured them with valid public TLS certificates.
This guide includes:
✅ Azure DNS zone setup
✅ A record configuration
✅ cert-manager configuration
✅ TLS-enabled Ingress
✅ Complete troubleshooting section
🏗 Architecture Overview
User (HTTPS)
↓
Azure Public IP
↓
Azure Load Balancer
↓
NGINX Ingress Controller
↓
Kubernetes Services
↓
Pods
For certificate issuance:
cert-manager
↓
Let's Encrypt (HTTP-01 challenge)
↓
Temporary solver ingress
↓
Validation
↓
TLS Secret stored in cluster
🌍 Step 1 — Create Azure DNS Zone
If you don’t already have the zone:
az network dns zone create \
--resource-group <dns-rg> \
--name az.innopy.dev
Verify:
az network dns zone list -o table
🌐 Step 2 — Point Subdomains to AKS Ingress
Get your NGINX public IP:
kubectl get svc -n ingress-nginx
Set variables:
RG_DNS=<dns-rg>
DNS_ZONE=az.innopy.dev
INGRESS_IP=<public-ip>
Create A record for hello:
az network dns record-set a create \
--resource-group $RG_DNS \
--zone-name $DNS_ZONE \
--name hello \
--ttl 300
az network dns record-set a add-record \
--resource-group $RG_DNS \
--zone-name $DNS_ZONE \
--record-set-name hello \
--ipv4-address $INGRESS_IP
Repeat for api:
az network dns record-set a create \
--resource-group $RG_DNS \
--zone-name $DNS_ZONE \
--name api \
--ttl 300
az network dns record-set a add-record \
--resource-group $RG_DNS \
--zone-name $DNS_ZONE \
--record-set-name api \
--ipv4-address $INGRESS_IP
Verify propagation:
dig +short hello.az.innopy.dev
dig +short api.az.innopy.dev
Must return your ingress public IP.
🔐 Step 3 — Install cert-manager
Install CRDs and controller (recommended via Helm in production).
Verify:
kubectl get pods -n cert-manager
All pods must be Running.
🔑 Step 4 — Create Let’s Encrypt ClusterIssuer
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-production
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
email: admin@innopy.dev
privateKeySecretRef:
name: letsencrypt-production-account-key
solvers:
- http01:
ingress:
class: nginx
Apply:
kubectl apply -f clusterissuer.yaml
🌍 Step 5 — Create TLS-enabled Ingress
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: chromia-ingress
annotations:
cert-manager.io/cluster-issuer: "letsencrypt-production"
spec:
ingressClassName: nginx
tls:
- hosts:
- hello.az.innopy.dev
- api.az.innopy.dev
secretName: az-chromia-tls
rules:
- host: hello.az.innopy.dev
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: hello-service
port:
number: 80
- host: api.az.innopy.dev
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: api-service
port:
number: 80
Apply:
kubectl apply -f ingress.yaml
🔎 Step 6 — Monitor Certificate Issuance
kubectl get certificate
kubectl get challenges
kubectl get orders
Successful issuance will show:
READY: True
Order completed successfully
🌐 Step 7 — Test HTTPS
curl -v https://hello.az.innopy.dev
curl -v https://api.az.innopy.dev
You should see:
SSL certificate verify ok.
issuer: Let's Encrypt
🎉 You now have real public TLS on AKS.
🚨 Complete Troubleshooting Guide
Here are the real-world issues we hit and how to fix them.
❌ Challenge Stuck in Pending
Check:
kubectl describe challenge <name>
Most common causes:
1️⃣ DNS not resolving
dig +short hello.az.innopy.dev
Must match ingress IP exactly.
2️⃣ Port 80 not reachable
Let’s Encrypt must access:
http://hello.az.innopy.dev/.well-known/acme-challenge/<token>
Test:
curl http://hello.az.innopy.dev
3️⃣ HTTPS Redirect Blocking HTTP-01
If you have:
nginx.ingress.kubernetes.io/ssl-redirect: "true"
Remove it during certificate issuance.
Let HTTP remain open.
After cert is issued, you can re-enable redirect.
4️⃣ Wrong Ingress Class
Your ClusterIssuer must match:
ingressClassName: nginx
If class mismatches → solver ingress not created.
5️⃣ No Solver Ingress Created
Check:
kubectl get ingress
You should see:
cm-acme-http-solver-xxxx
If not → cert-manager RBAC or config issue.
6️⃣ cert-manager Logs
kubectl logs -n cert-manager deploy/cert-manager
Look for:
- permission errors
- webhook errors
- ACME failures
🔄 Certificate Renewal
cert-manager automatically renews certificates before expiration (usually at ~60 days).
Check expiry:
kubectl describe certificate az-chromia-tls
No manual action required.
🏁 Final Result
You now have:
✅ Azure-managed DNS
✅ Public subdomains
✅ AKS-hosted applications
✅ Automated certificate issuance
✅ Automatic renewal
✅ Production-grade HTTPS
This setup is fully cloud-native and scalable.
Top comments (0)