Phase 5 - AKS with Azure DNS + NGINX Ingress + cert-manager

Published: (March 1, 2026 at 12:55 AM EST)
4 min read
Source: Dev.to

Source: Dev.to

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.dev
  • api.az.innopy.dev

and secured them with valid public TLS certificates.

Guide Overview

  • ✅ 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

Certificate issuance flow

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  \
  --name az.innopy.dev

Verify:

az network dns zone list -o table

🌐 Step 2 — Point Subdomains to AKS Ingress

  1. Get your NGINX public IP:

    kubectl get svc -n ingress-nginx
  2. Set variables (replace placeholders with your values):

    RG_DNS=
    DNS_ZONE=az.innopy.dev
    INGRESS_IP=
  3. 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
  4. 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
  5. Verify propagation:

    dig +short hello.az.innopy.dev
    dig +short api.az.innopy.dev

Both commands must return your ingress public IP.

🔐 Step 3 — Install cert‑manager

Install CRDs and controller (recommended via Helm in production). Then verify:

kubectl get pods -n cert-manager

All pods should be Running.

🔑 Step 4 — Create Let’s Encrypt ClusterIssuer

clusterissuer.yaml

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

ingress.yaml

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

❌ Challenge Stuck in Pending

Check the challenge details:

kubectl describe challenge 

Most common causes

1️⃣ DNS not resolving

dig +short hello.az.innopy.dev

The result must match the ingress IP exactly.

2️⃣ Port 80 not reachable

Let’s Encrypt must be able to access:

http://hello.az.innopy.dev/.well-known/acme-challenge/

Ensure any network security groups, firewalls, or NGINX configurations allow inbound HTTP traffic on port 80.

Test Connectivity

curl http://hello.az.innopy.dev

HTTPS Redirect Blocking HTTP‑01

If you have the annotation:

nginx.ingress.kubernetes.io/ssl-redirect: "true"

Action: Remove it while the certificate is being issued. Re‑enable the redirect after the cert is issued.

Wrong Ingress Class

Your ClusterIssuer must specify the same ingress class that your controller uses, e.g.:

ingressClassName: nginx

A mismatch prevents the solver ingress from being created.

No Solver Ingress Created

Check existing ingresses:

kubectl get ingress

You should see an ingress similar to cm-acme-http-solver-xxxx. If it’s missing, there may be an RBAC or configuration issue with cert‑manager.

cert‑manager Logs

kubectl logs -n cert-manager deploy/cert-manager

Look for permission errors, webhook errors, or ACME failures.

🔄 Certificate Renewal

cert-manager automatically renews certificates before they expire (typically around day 60 of a 90‑day cert).

Check the certificate’s expiry date:

kubectl describe certificate az-chromia-tls

No manual action is required; renewal happens automatically.

🏁 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.

0 views
Back to Blog

Related posts

Read more »

Google Gemini Writing Challenge

What I Built - Where Gemini fit in - Used Gemini’s multimodal capabilities to let users upload screenshots of notes, diagrams, or code snippets. - Gemini gener...