ingress-nginx with TLS and cert-manager
Introduction¶
An Ingress is a set of L7 routing rules (host + path → Service). An Ingress
controller — commonly ingress-nginx — watches Ingress objects and configures
an actual reverse proxy. cert-manager automates certificate issuance so you
never touch a .pem file.
Install¶
helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
helm install ingress-nginx ingress-nginx/ingress-nginx \
-n ingress-nginx --create-namespace
helm repo add jetstack https://charts.jetstack.io
helm install cert-manager jetstack/cert-manager \
-n cert-manager --create-namespace --set crds.enabled=true
kubectl get svc -n ingress-nginx — the ingress-nginx-controller Service gets
an external IP (cloud LB) or a NodePort (bare metal + MetalLB). Point your DNS
A/AAAA records at it.
ClusterIssuer¶
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-prod
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
email: [email protected]
privateKeySecretRef:
name: letsencrypt-prod-account-key
solvers:
- http01:
ingress:
class: nginx
Start with the staging server
(https://acme-staging-v02.api.letsencrypt.org/directory) while you get the
setup right — production has tight rate limits.
Ingress with TLS¶
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: app
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
ingressClassName: nginx
tls:
- hosts: [app.example.com]
secretName: app-tls # cert-manager creates this
rules:
- host: app.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: web
port:
number: 80
- path: /api
pathType: Prefix
backend:
service:
name: api
port:
number: 80
cert-manager sees the annotation + tls: block, creates a Certificate,
solves the HTTP-01 challenge (it injects a temporary path into your Ingress),
and writes the cert into app-tls. ingress-nginx picks it up.
Useful annotations¶
nginx.ingress.kubernetes.io/proxy-body-size: "50m"
nginx.ingress.kubernetes.io/ssl-redirect: "true" # default true when TLS present
nginx.ingress.kubernetes.io/rewrite-target: /$2 # with a path regex capture
nginx.ingress.kubernetes.io/proxy-read-timeout: "3600" # websockets/SSE
nginx.ingress.kubernetes.io/enable-cors: "true"
Verification and troubleshooting¶
kubectl get ingress
kubectl describe ingress app
kubectl get certificate,certificaterequest,order,challenge -A
kubectl describe challenge <name> # why HTTP-01 is failing
kubectl logs -n ingress-nginx deploy/ingress-nginx-controller
curl -vI https://app.example.com
- 404 from ingress-nginx ("default backend - 404") — no Ingress matched:
wrong
host, missingingressClassName, Ingress in a different namespace than the Service, or the Service has no endpoints. CertificatestuckFalse/Issuing— check thechallengeobject. Common: DNS not pointing at the ingress IP yet, HTTP-01 path not reachable (assl-redirectloop, or a WAF blocking/.well-known/acme-challenge/), or Let's Encrypt rate-limited (you were on prod while testing).503 Service Temporarily Unavailable— backend Service has no ready endpoints.kubectl get endpoints <svc>.- TLS works but redirects to
http://— app behind ingress doesn't know it's HTTPS; it should trustX-Forwarded-Proto(ingress-nginx sets it). - Path routing sends everything to one backend —
pathType: Prefixmatching is longest-prefix;/apiand/both match/api/xand the more specific wins. If using regex, you needpathType: ImplementationSpecific+use-regexannotation. - Large uploads fail at ~1 MB — default
proxy-body-size. Raise it via annotation. - cert-manager: "no configured challenge solvers" — the
solversselector in the issuer doesn't match; simplest is one solver with no selector.
Related tools and reading¶
- On-site: SSL Certificate Checker, DNS lookup, HTTP Header Analyzer.
- Related posts: Kubernetes Services and Endpoints, Let's Encrypt with certbot and renewal.
Stuck on something this site can't fix?Reach out to Prabath directly on LinkedIn.More in Docker & Kubernetes
StatefulSets Explained
StatefulSets give pods sticky names, sticky storage, and ordered operations — what databases and clustered apps need. Here's how each guarantee works.
October 24, 2026Persistent Volumes and Claims, Explained
PVC is a request, PV is the fulfilled storage, StorageClass is the factory. Most 'Pending PVC' problems are an access-mode or provisioner mismatch.
October 24, 2026Kubernetes Jobs and CronJobs
A Job runs pods until they succeed; a CronJob creates Jobs on a schedule. Here's parallelism, retry limits, the missed-schedule trap, and history cleanup.
October 24, 2026