Search across all documentation pages
Ingress objects and programs a real proxy or cloud load balancer to route traffic.Ingress API is inert. The controller is what turns YAML into actual HTTP routing.Install a controller, confirm its IngressClass, then reference that class from your Ingress objects.
Most teams run exactly one general-purpose controller per cluster and scope any others by class.
helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
helm install ingress-nginx ingress-nginx/ingress-nginx \
--namespace ingress-nginx --create-namespace \
--set controller.replicaCount=2
kubectl get ingressclassDeploy ingress-nginx with two replicas for availability, then verify the class and external address.
kubectl -n ingress-nginx get deploy,svcThe controller's own Service is usually type: LoadBalancer, which provisions a cloud load balancer as the public entry point.
An Ingress then binds to the controller by class:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: web
spec:
ingressClassName: nginx
rules:
- host: app.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: web
port:
number: 80Confirm the controller claimed it and assigned an address:
kubectl describe ingress webThe events should show the controller syncing, and ADDRESS should populate once the load balancer is ready.
The controller runs a control loop. It watches Ingress, Service, EndpointSlice, and Secret objects, computes a proxy configuration, and reloads or hot-updates its data plane.
For ingress-nginx the data plane is nginx. For Traefik it is Traefik's own proxy. For the AWS Load Balancer Controller it is an ALB provisioned in your account.
IngressClass is the link between an Ingress object and a controller.
apiVersion: networking.k8s.io/v1
kind: IngressClass
metadata:
name: nginx
annotations:
ingressclass.kubernetes.io/is-default-class: "true"
spec:
controller: k8s.io/ingress-nginxAn object with ingressClassName: nginx is served only by the controller whose spec.controller matches. The default-class annotation lets objects that omit the field be claimed automatically.
Two controllers watching the same IngressClass will both try to serve those objects, causing conflicts and duplicate load balancers.
The common pattern is one general controller for most traffic, plus optionally a second controller scoped to its own class (for example an internal-only class for private LBs).
Scope cleanly: distinct IngressClass names, and if needed the --ingress-class or watch-namespace flags so each controller only reconciles its own objects.
ingress-nginx is the community default, portable across clouds, rich annotation set, runs as pods.
AWS Load Balancer Controller provisions ALBs directly, so L7 routing happens in AWS rather than in-cluster pods; ideal on EKS.
Traefik offers a strong Kubernetes-native CRD model and good Gateway API support.
Envoy-based controllers (Contour, Emissary) give advanced L7 features and are natural on-ramps to Gateway API.
Run at least two controller replicas with a PodDisruptionBudget and spread constraints.
The proxy replicas serve traffic in parallel; leader election (where used) only coordinates control-plane bookkeeping, not the data path.
No controller installed. Applying an Ingress with no controller present does nothing and produces no error. Always confirm a controller and matching IngressClass exist.
Wrong or missing ingressClassName. If the name does not match an installed class, no controller claims the object. With no default class set, omitting the field also leaves it unclaimed.
Two controllers fighting. Two controllers sharing a class create duplicate cloud load balancers and inconsistent routing. Give each its own class.
Annotation drift. Copying annotations between controllers rarely works, since keys like nginx.ingress.kubernetes.io/* are ignored by Traefik or ALB. Re-map features per controller.
Reload cost under churn. Some controllers reload the proxy on config changes. Heavy Ingress churn can cause reload storms; prefer controllers with dynamic reconfiguration for high-change environments.
Assuming Docker runs the proxy. The controller pods run on nodes under containerd via the CRI, not Docker Engine. Docker is only for building the images.
Gateway API with a Gateway controller. The successor to Ingress, with a role-oriented, more expressive API. Prefer it for new advanced routing; many controllers support both.
Service type LoadBalancer only. Pure L4 exposure without host or path routing or TLS SNI. Simpler, but no HTTP intelligence.
Service mesh gateway. Istio or Linkerd gateways handle north-south traffic alongside mesh features. Choose when you already run a mesh.
API gateway product. A dedicated gateway (Kong, cloud API gateway) when you need auth, rate limiting, and developer portals beyond routing.
Can I run more than one ingress controller? Yes, if each watches a distinct IngressClass (or namespace). A common split is public and internal-only controllers.
Does every Ingress need ingressClassName? Set it explicitly for clarity. You can rely on a default IngressClass, but explicit binding avoids surprises.
Why is my Ingress ADDRESS empty? The controller has not reconciled it, the class does not match, or the cloud load balancer is still provisioning.
Is ingress-nginx the same as nginx Inc's controller? No. ingress-nginx is the Kubernetes community controller. F5/NGINX ships a separate product. They differ in annotations and features.
Does the controller terminate TLS? Typically yes. It holds the certificate and decrypts requests, then forwards to pods. See the TLS certificates page.
Stack versions: This page was written for Kubernetes 1.36.2, Docker Engine 29.6.1 (BuildKit default), containerd (CRI runtime on nodes), Helm 3, Compose v2, Argo CD (latest - verify at build), and Gateway API (GA - verify controller support at build).
Reviewed by Chris St. John·Last updated Jul 16, 2026