Ingress and Ingress Controllers¶
What You'll Learn¶
- Why an
Ingressmanifest does nothing by itself, and what actually makes it work - How to route by host and path to multiple backend Services from one entry point
- How TLS termination and automatic certificate issuance with cert-manager fit together
ingress-nginx is retired
The Kubernetes project retired the ingress-nginx controller in March 2026 — it no longer receives releases or security fixes. The Ingress API itself is not deprecated, so everything on this page about how Ingress works still applies, but for a new cluster pick a maintained controller or go straight to Gateway API, and plan a migration for existing ingress-nginx installs.
Why This Matters¶
Ingress is one of the most misunderstood objects in Kubernetes because it's declarative config, not a running component. Applying it with no controller installed produces an object that sits in etcd doing nothing — no errors, no traffic routed, nothing. Understanding this split is the difference between debugging in five minutes and an hour of confused curl attempts.
Mental Model¶
Ingressis a specification — "route this host/path to that Service." An Ingress controller is a running workload (usually a Deployment of a reverse proxy) that watchesIngressobjects via the API server and configures itself to actually match them. No controller, no effect, regardless of how correct your YAML is.
flowchart LR
U[User request: https://app.example.com/api] --> LB[Cloud/external Load Balancer]
LB --> IC[Ingress controller pod - e.g. Traefik]
IC -->|reads Ingress objects via API server| API[Kubernetes API Server]
IC -->|routes by host+path| S1[Service: api-service]
IC -->|routes by host+path| S2[Service: frontend-service]
How It Works¶
The Ingress resource¶
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: app-ingress
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
ingressClassName: nginx
tls:
- hosts:
- app.example.com
secretName: app-tls
rules:
- host: app.example.com
http:
paths:
- path: /api
pathType: Prefix
backend:
service:
name: api-service
port:
number: 8080
- path: /
pathType: Prefix
backend:
service:
name: frontend-service
port:
number: 80
ingressClassNametells Kubernetes which installed controller should handle this object — required once more than one controller might be present in a cluster.pathType: Prefixmatches/api,/api/,/api/anything—Exactmatches only the literal path;ImplementationSpecificdefers matching semantics to the controller.- Annotations are how controller-specific behavior (rewrites, rate limits, auth) gets configured — they aren't part of the core
Ingressspec, which is deliberately minimal.
Installing an Ingress controller¶
Nothing above works until a controller is actually running:
helm repo add traefik https://traefik.github.io/charts
helm repo update
helm install traefik traefik/traefik \
--namespace traefik --create-namespace
kubectl get pods -n traefik
kubectl get svc -n traefik # usually a LoadBalancer Service — the real external entry point
kubectl get ingressclass # the class name to use in ingressClassName
The manifests on this page use ingressClassName: nginx and nginx.ingress.kubernetes.io/* annotations because that is what most existing clusters still run. With Traefik, set ingressClassName: traefik and drop the nginx annotations — which is exactly the annotation-portability problem described below.
Popular controllers:
| Controller | Notes |
|---|---|
| ingress-nginx | Retired March 2026 — historically the most widely deployed; migrate off it |
| NGINX Gateway Fabric / F5 NGINX Ingress Controller | Maintained NGINX-based options (different projects from ingress-nginx, different annotations) |
| Traefik | Native support for dynamic config, good with Let's Encrypt out of the box, popular in smaller/edge deployments |
| HAProxy Ingress | Strong for teams already standardized on HAProxy |
| Cloud-native (ALB Ingress Controller, GKE Ingress) | Maps Ingress directly onto the cloud provider's native load balancer, no separate proxy pod |
TLS termination with cert-manager¶
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-prod
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
email: platform-team@example.com
privateKeySecretRef:
name: letsencrypt-prod-key
solvers:
- http01:
ingress:
ingressClassName: nginx
With cert-manager installed and a ClusterIssuer configured, the cert-manager.io/cluster-issuer annotation on an Ingress (as in the example above) is enough: cert-manager watches for that annotation, requests a certificate from Let's Encrypt via the ACME HTTP-01 challenge (served through the same Ingress controller), and populates the secretName referenced in tls: automatically — including renewal before expiry.
Common Mistakes¶
- Applying an
Ingressmanifest and expecting it to work with no controller installed — checkkubectl get pods -n <ingress-namespace>first when nothing routes. - Omitting
ingressClassNamein a cluster with more than one controller — theIngressmay be picked up by the wrong one, or none, depending on default-class configuration. - Mismatched
pathTypeexpectations — assumingPrefixbehaves like a glob, when it matches on/-delimited path segments, not substrings. - Forgetting that annotations are controller-specific — an
nginx.ingress.kubernetes.io/*annotation does nothing under Traefik, and vice versa. - Treating cert-manager's ACME HTTP-01 challenge as automatic without the Ingress controller actually reachable from the public internet — the challenge fails silently if DNS/firewall isn't pointed at the controller yet.
Interview Questions¶
- Why does an
Ingressobject do nothing without an Ingress controller, and how would you verify one is installed? - Walk through what happens end to end when cert-manager issues a certificate for an
Ingress. - What's the difference between
pathType: Prefix,Exact, andImplementationSpecific?
See Interview Prep for full answers.
Next¶
Continue to Network Policies to restrict which traffic is even allowed to reach these Services in the first place. The modern successor to this whole page is covered in Gateway API.