ivinco
Kubernetes Ingress in 2026: Examples and Controllers

Kubernetes Ingress in 2026: Examples and Controllers

Ivinco Team·

What is Ingress in Kubernetes?

Kubernetes Ingress is an API object that routes HTTP and HTTPS traffic from outside the cluster to Services inside it. Rules match the request's hostname and URL path, and an Ingress can also terminate TLS. The object does nothing alone. An Ingress controller reads it and configures a proxy or cloud load balancer to match.

Teams use Ingress to put many web Services behind one address. The docs say this keeps the number of load balancers "down to a minimum".

In 2026 the Ingress API is stable and frozen, and no removal is planned. The community ingress-nginx controller, which about half of cloud native environments relied on, was retired in March 2026. The deprecation section below explains what changed and what keeps working. This guide reflects Kubernetes 1.37, the current release, and sources checked in October 2026.

How Kubernetes Ingress works

The Ingress resource holds the rules, and a controller puts them into effect. An IngressClass links the two. A default backend answers requests that match no rule.

The Ingress resource

An Ingress holds a list of rules. Each rule has an optional host and a list of paths, and each path points to a backend Service and port. That Service must be in the same namespace as the Ingress. An optional tls section lists the hosts to secure and the Secret that holds their certificate.

Anything beyond hosts, paths and TLS goes into annotations. Each controller defines its own, so the docs tell you to check which ones yours supports.

The Ingress controller

For Ingress to work, an Ingress controller must be running. You choose it and set it up yourself, unless your platform ships one. The controller watches Ingress objects and turns them into load balancer configuration.

In-cluster controllers usually run as pods behind one LoadBalancer Service. Cloud controllers, such as the AWS Load Balancer Controller, create the cloud load balancer themselves.

IngressClass and ingressClassName

A cluster can run several controllers. Each one registers an IngressClass, and spec.ingressClassName on an Ingress picks one. Leave the field out and Kubernetes applies the default class, the one with the annotation ingressclass.kubernetes.io/is-default-class: "true". If several classes carry it, Kubernetes picks the newest, so mark only one.

ingressClassName replaced the kubernetes.io/ingress.class annotation in Kubernetes 1.18, and the annotation is deprecated. GKE is an exception. Its built-in controller still selects Ingresses by that annotation, with the value gce or gce-internal. It leaves an Ingress that sets only ingressClassName to other controllers.

The default backend

Requests that match no host or path go to the default backend. You can set one in spec.defaultBackend, but it is usually a controller option. Without one, the controller decides what to return. The ingress-nginx default backend, for example, returned a 404 for every path except its health check.

What happens to a request

Take a request for https://shop.example.com/api/orders through an in-cluster controller:

  1. DNS resolves shop.example.com to the controller's load balancer.
  2. The load balancer passes the connection to the controller's proxy.
  3. The proxy terminates TLS with the certificate from the Ingress's Secret.
  4. It matches the Host header and the path against the rules and picks a backend Service.
  5. It forwards the request to one of that Service's ready pods.

Many controllers send traffic straight to pod IPs, as our load balancing guide explains. The last hop is plain HTTP by default, because the Ingress API assumes TLS ends at the controller.

Kubernetes Ingress example (YAML)

This manifest serves shop.example.com over HTTPS and splits it between two Services. It uses networking.k8s.io/v1, the only Ingress API version served since Kubernetes 1.22.

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: shop
  namespace: shop
spec:
  ingressClassName: example
  tls:
    - hosts:
        - shop.example.com
      secretName: shop-tls
  rules:
    - host: shop.example.com
      http:
        paths:
          - path: /api
            pathType: Prefix
            backend:
              service:
                name: api
                port:
                  number: 8080
          - path: /
            pathType: Prefix
            backend:
              service:
                name: web
                port:
                  number: 80

What each part does, according to the Ingress docs:

  • ingressClassName picks the controller. Replace example with a class from kubectl get ingressclass.
  • tls names the Secret with the certificate. The Secret must contain tls.crt and tls.key, and the hosts listed here must match the rule's host.
  • host is an exact name or a wildcard such as *.example.com. A wildcard covers exactly one DNS label.
  • pathType is required on every path. Exact matches one path, and Prefix matches whole path segments. Both are case-sensitive. ImplementationSpecific leaves matching to the controller.
  • backend names a Service in the same namespace and its port, by number or name.

When several paths match, the longest wins, and Exact beats Prefix on a tie. Here /api and /api/orders go to the api Service. /apiary goes to web, because Prefix compares whole segments.

Create the Secret from a certificate and key you already have:

kubectl create secret tls shop-tls -n shop \
  --cert=tls.crt --key=tls.key

Keep it in the Ingress's namespace, since the tls section has no namespace field. cert-manager can issue and renew the certificate instead. Add its issuer annotation to the Ingress, and it stores the certificate in the Secret named in secretName.

How to test an Ingress

kubectl apply -f shop-ingress.yaml
kubectl get ingress shop -n shop

The ADDRESS column fills in once the controller has a load balancer, which can take a minute or two. Test the routing before DNS exists by putting that address in place of 203.0.113.10:

curl -H "Host: shop.example.com" \
  http://203.0.113.10/api/orders
curl --resolve shop.example.com:443:203.0.113.10 \
  https://shop.example.com/

The Host header is enough for plain HTTP, though some controllers redirect to HTTPS when tls is set, as ingress-nginx did. For HTTPS, --resolve works like a one-off /etc/hosts entry, so TLS still sees the real hostname. On AWS, ADDRESS is a hostname, so look up its IP with dig +short first.

How to get Ingress with kubectl

kubectl get ingress lists the Ingresses in the current namespace, and ing is its short name. Add -A for all namespaces. These four commands answer most questions:

kubectl get ingress -A
kubectl describe ingress shop -n shop
kubectl get ingress shop -n shop -o yaml
kubectl get ingressclass

get prints NAME, CLASS, HOSTS, ADDRESS, PORTS and AGE. For Ingress, -o wide adds no columns. None of the six is marked as a wide column in the table printer that the API server runs for kubectl get. CLASS <none> means ingressClassName is empty. Either no class was set and no default applied, or the Ingress uses the old annotation, as on GKE. A blank ADDRESS means no controller has published an address yet. PORTS reads 80, 443 whenever the spec has a tls section, whatever the controller actually opened.

describe is the most useful single check. Under Rules, it lists each backend with its ready pod addresses in parentheses, such as api:8080 (10.8.0.90:8080). An error there usually means the Service is missing. Empty parentheses or (<none>) mean kubectl found no ready pod for that backend. A port the Service doesn't define can show the same, so check the port too. The Events at the bottom show what the controller reported, if it reports events.

-o yaml shows the spec and status.loadBalancer.ingress, where the controller writes the address.

get ingressclass shows which controllers the cluster is set up for, in the CONTROLLER column, such as ingress.k8s.aws/alb or k8s.io/ingress-nginx. The class your Ingress names must appear here, and its controller must be running.

The controller's logs explain rejected objects:

kubectl logs -n <controller-namespace> \
  deployment/<controller-name> --tail=100

F5's NGINX Ingress Controller, for example, ignores an Ingress with invalid annotations, while the object stays in the cluster.

Ingress vs LoadBalancer vs NodePort

All three bring outside traffic to pods. NodePort and LoadBalancer are Service types that work at layer 4, one Service per port or address. Ingress works at layer 7 and puts many HTTP Services behind one address.

NodePortLoadBalancerIngress
LayerL4L4L7
TrafficTCP, UDPTCP, UDPHTTP, HTTPS
Entry pointA port on every nodeAn IP per ServiceOne shared address
Routes byPortPortHost and path
NeedsNothing extraCloud LB or MetalLBA controller

They often stack. An in-cluster Ingress controller usually sits behind one LoadBalancer Service, and every HTTP Service behind it shares that address. For other protocols, the Ingress docs point to NodePort or LoadBalancer Services. NodePort uses ports 30000-32767 by default and suits testing or your own external load balancer. Our Kubernetes load balancing guide covers Service types, cloud load balancers and bare metal in depth.

Ingress vs egress in Kubernetes

Ingress is traffic coming in, and egress is traffic going out. The Ingress object routes incoming HTTP and HTTPS traffic from outside the cluster. NetworkPolicy uses both words as directions. Its ingress rules limit connections into pods, and its egress rules limit connections out of them. By default, pods allow both, and policies only work with a network plugin that enforces them.

The two meet when you lock a namespace down. If your app's namespace denies inbound traffic by default, as our networking guide recommends, let the controller in:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-ingress-controller
  namespace: shop
spec:
  podSelector: {}
  policyTypes: ["Ingress"]
  ingress:
    - from:
        - namespaceSelector:
            matchLabels:
              kubernetes.io/metadata.name: ingress-system

Replace ingress-system with your controller's namespace, whose name Kubernetes sets as a label. This works for controllers that run as ordinary pods. A controller on the host network may not match a namespace selector. Cilium Ingress is different too. Its traffic reaches pods from a special ingress identity, which Cilium's docs say your policies must allow. Cloud load balancers that reach pod IPs directly come from outside the cluster, so allow their ranges instead. GKE's external Application Load Balancer, for example, reaches pods from 130.211.0.0/22 and 35.191.0.0/16.

Kubernetes Ingress controllers in 2026

The Kubernetes project maintains two controllers, for AWS and Google Cloud, and lists about 30 third-party ones. The Gateway API project's guide for ingress-nginx users suggests choosing by the proxy your team knows and by what your cloud or CNI offers. That test works for Ingress too.

Your situationControllers to look at
You run on one cloudAWS Load Balancer Controller, GKE Ingress (maintenance mode), Application Gateway for Containers
Your team knows EnvoyContour, Emissary-Ingress, Gloo
Your team knows NGINXF5 NGINX Ingress Controller
Your team knows HAProxyHAProxy Kubernetes Ingress Controller, N42 Gateway
You already run Traefik, Kong or IstioIts own Ingress support
Cilium is your CNICilium Ingress

Many of these also implement Gateway API, which makes a later move easier. F5 covers it with a separate project, NGINX Gateway Fabric.

How to install an Ingress controller

Each controller's docs give its install steps, usually a Helm chart. On EKS, GKE and AKS, see the platform options below. Then check that the new class exists and, for an in-cluster controller, that its Service has an EXTERNAL-IP:

kubectl get ingressclass
kubectl get service -n <controller-namespace>

Many tutorials still install ingress-nginx, which is retired and gets no security fixes. Pick a maintained controller from the table above. For a local test, kind clusters with Cloud Provider KIND v0.9.0 or later serve Ingress with no extra controller.

Ingress on EKS, GKE and AKS

PlatformIngress optionStatus
EKSAWS Load Balancer ControllerYou install it
EKS Auto ModeBuilt-in ALB supportIncluded
GKEGKE IngressMaintenance mode
AKSApplication routing add-on (NGINX)Supported to November 2026
AKSApplication Gateway for ContainersIngress and Gateway API

EKS. The AWS Load Balancer Controller creates an Application Load Balancer per Ingress, or shares one across an IngressGroup. Its IngressClasses use the controller name ingress.k8s.aws/alb. EKS Auto Mode builds this in, under the name eks.amazonaws.com/alb.

GKE. GKE Ingress creates Application Load Balancers. Its docs now put it in maintenance mode, with bug fixes only, and recommend GKE's Gateway API controller.

AKS. The application routing add-on runs managed NGINX based on ingress-nginx, with the class webapprouting.kubernetes.azure.com. Microsoft gives it critical security patches through November 2026, and its users must migrate to the add-on's Gateway API implementation, or another supported one, by then. Application Gateway for Containers runs its data plane outside the cluster, driven by an ALB Controller inside it. It accepts both Ingress and Gateway API resources.

Is NGINX an ingress controller?

Not by itself. NGINX is a web server and reverse proxy, and several Ingress controllers run it as their proxy. Two of them carry its name. The Kubernetes blog notes that both use NGINX as the data plane "but are otherwise unrelated".

ingress-nginxF5 NGINX Ingress Controller
OwnerKubernetes communityF5
StatusRetired in March 2026Maintained, v5.6.3 in September 2026
Annotationsnginx.ingress.kubernetes.io/nginx.org/, nginx.com/
Repositorykubernetes/ingress-nginxnginx/kubernetes-ingress

F5's controller works with NGINX or NGINX Plus.

Is Kubernetes Ingress deprecated?

No. The Ingress API, networking.k8s.io/v1, has been stable since Kubernetes 1.19 and keeps the guarantees of a GA API. It is also frozen, so it gets no new features, and the project recommends Gateway API instead. The Ingress docs say the project has "no plans to remove Ingress from Kubernetes". What retired in March 2026 was one controller, ingress-nginx.

Several related things did change:

ItemStatus in October 2026
Ingress API, networking.k8s.io/v1GA and frozen, no removal planned
Beta Ingress APIs (v1beta1)Not served since 1.22
kubernetes.io/ingress.class annotationDeprecated in favor of ingressClassName
ingress-nginx controllerRetired, no more fixes
GKE Ingress controllerMaintenance mode, bug fixes only
AKS managed NGINX add-onCritical security patches through November 2026

What the ingress-nginx retirement means

ingress-nginx was the community controller from the Kubernetes project. Its retirement came in four steps:

  • November 11, 2025. SIG Network and the Security Response Committee announced it.
  • January 29, 2026. The Steering and Security Response Committees warned that none of the alternatives is a drop-in replacement.
  • March 19, 2026. The final releases, controller v1.15.1, v1.14.5 and v1.13.9, came out.
  • March 24, 2026. The repository was archived and became read-only.

Since then there have been "no further releases, no bugfixes, and no updates to resolve any security vulnerabilities". Its planned successor, InGate, never matured and is retired too. Existing installs keep running, and the Helm charts and images stay available. The statement warns that unless you check, "you may not know you are affected until you are compromised."

The announcement gives this check, which needs cluster-wide read access and works in most cases:

kubectl get pods --all-namespaces \
  --selector app.kubernetes.io/name=ingress-nginx

In kubectl get ingressclass, its classes show the controller k8s.io/ingress-nginx by default. Replacing it is item 25 on our Kubernetes security checklist.

How to migrate off ingress-nginx

The retirement notice recommends moving to Gateway API or to another Ingress controller. Both take planning.

Another Ingress controller. Your Ingress objects stay, and their rules and TLS settings carry over once you change ingressClassName. The annotations don't, because each controller defines its own. The Gateway API docs call the result "limited portability". URL rewriting shows the gap:

ControllerRewrite setting
ingress-nginxnginx.ingress.kubernetes.io/rewrite-target
F5 NGINX Ingress Controllernginx.org/rewrite-target or nginx.org/rewrites
HAProxy Kubernetes Ingress Controllerhaproxy.org/path-rewrite
TraefikA Middleware, attached with traefik.ingress.kubernetes.io/router.middlewares

Default behavior differs too. A Kubernetes blog post lists ingress-nginx habits that a faithful copy can break:

  • regex paths are case-insensitive prefix matches;
  • a rewrite-target annotation quietly turns every path on that host into a regex;
  • a request for /my-path is redirected to /my-path/ when only the slash version is defined;
  • URLs are normalized before matching.

Traefik's documentation says its Ingress NGINX provider supports "many of the ingress-nginx annotations". Whichever controller you pick, test each annotation you rely on.

Gateway API. This is the path the Kubernetes project recommends. ingress2gateway, a SIG Network tool, converts Ingress resources and their annotations into Gateway API resources. It supports over 30 common ingress-nginx annotations, with v1.2.0 out since July 2026. It also reads setups written for F5's controller, Traefik, Kong, Istio, Cilium, APISIX and GKE.

Read its warnings, which flag what did not translate, such as configuration-snippet annotations. Run the new gateway next to the old controller, where it gets its own external IP, and shift traffic over gradually. Our load balancing guide lists the full migration steps.

If ingress-nginx or the AKS add-on still serves your traffic, our Kubernetes consulting team starts with read-only access and a written list of what to fix first.

Ingress vs Gateway API

Gateway API is the successor to the Ingress API. Both route outside traffic to Services. Ingress is one frozen object for HTTP and HTTPS, and anything more needs controller-specific annotations. Gateway API splits the setup by role, adds gRPC, TLS, TCP and UDP routes, and puts redirects, header matching and traffic splitting in the spec.

Gateway API ships as CRDs, so you install it or get it with your implementation. Its core went GA with v1.0 in October 2023, and as of October 2026 the latest release is v1.6.3.

What it adds over Ingress:

  • Roles. A cluster operator owns the Gateway, with its listeners and certificates. App teams attach HTTPRoutes to it, from other namespaces too when the Gateway allows it.
  • More protocols. HTTPRoute and GRPCRoute are stable. TLSRoute joined the standard channel in v1.5, and TCPRoute and UDPRoute in v1.6.
  • Portable features. Redirects, header changes, traffic splitting and matching on headers, query parameters or methods are part of the spec. In Ingress they need annotations.
  • Defined conflict rules. Gateway API specifies how rules from many routes merge. The Ingress API leaves that to each controller.

Here is the routing from the example above as an HTTPRoute. TLS moves to the Gateway, which the platform team owns:

apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: shop
  namespace: shop
spec:
  parentRefs:
    - name: shared-gateway
      namespace: infra
  hostnames:
    - shop.example.com
  rules:
    - matches:
        - path:
            type: PathPrefix
            value: /api
      backendRefs:
        - name: api
          port: 8080
    - backendRefs:
        - name: web
          port: 80

A rule with no matches defaults to a prefix match on /. The Gateway in infra must allow routes from shop in its allowedRoutes.

IngressGateway API
API statusGA, frozenGA, still growing
Installed asPart of KubernetesCRDs
ProtocolsHTTP, HTTPSHTTP, gRPC, TLS, TCP, UDP
Extra featuresAnnotationsSpec fields and policies
OwnershipOne objectSplit by role

When to move to Gateway API

Move when one of these is true:

  • You run ingress-nginx, or a platform add-on built on it.
  • You are building a new cluster or platform.
  • Several teams share one entry point and need separate permissions.
  • You need gRPC, TCP or UDP routing, or traffic splitting without annotations.
  • Your controller is in maintenance mode, as GKE Ingress is.

A maintained Ingress controller that serves simple host and path rules can stay for now. Before you switch, check your implementation's conformance report for each feature you need.

Troubleshooting Kubernetes Ingress

Start with kubectl describe ingress, then the controller's logs. Most problems fit one of these patterns:

SymptomLikely causeFirst check
ADDRESS stays blankNo controller for the classkubectl get ingressclass
404 from the controllerNo rule matchedHost header, pathType
503No ready podskubectl describe ingress
502 or 504Wrong port or a stuck podtargetPort, pod logs
Certificate warningTLS Secret missing or misplacedSecret name and namespace
Works by IP, not by nameDNS points elsewheredig +short

Ingress has no address

Usually no controller has claimed the Ingress yet. Some controllers also leave ADDRESS blank until told which address to publish, such as Traefik without its ingressEndpoint setting. Check that the Ingress's class appears in kubectl get ingressclass and that the controller's pods are running. Without ingressClassName, the Ingress gets a class only if one is marked default. If several are, it gets the newest, which may belong to a controller you didn't expect. The default also reaches only Ingresses created after it was set. On GKE, use the kubernetes.io/ingress.class annotation. An in-cluster controller usually gets its address from its own LoadBalancer Service, so check that Service's EXTERNAL-IP too.

404 Not Found from the default backend

A 404 that doesn't come from your app usually means no rule matched, so the default backend answered. Check three things:

  • The Host header. A request by IP, or for a name missing from the rules, matches no rule that sets a host. *.example.com matches neither example.com nor a.b.example.com.
  • The path type. Exact /foo doesn't match /foo/, and Prefix /foo doesn't match /foobar. ImplementationSpecific behaves however your controller decides.
  • The class. If no controller serves the Ingress's class, the proxy you are calling never received its rules.

503, 502 and 504 errors

These come from the proxy after a rule matched, and the codes vary by controller. Check in this order:

  1. kubectl describe ingress. Empty parentheses or (<none>) beside a backend mean no ready pods, or a port the Service doesn't define.
  2. The Service selector. kubectl get endpointslices -n shop -l kubernetes.io/service-name=web should list your pods. A label typo is a common cause.
  3. The ports. The Ingress must name a port the Service exposes, and targetPort must match the container port.
  4. Readiness. Pods that fail their readiness probes get no traffic.
  5. Network policies. A NetworkPolicy can block the controller from reaching the pods. The policy in the egress section lets it in.

TLS and certificate errors

Check the Secret first. It must live in the Ingress's namespace, and F5's controller, for one, requires that. It needs the type kubernetes.io/tls and the keys tls.crt and tls.key. The hosts under tls must match the rule hosts, and the docs warn that TLS will not work on a rule without a host.

Then check which certificate the controller actually serves:

openssl s_client -connect 203.0.113.10:443 \
  -servername shop.example.com </dev/null \
  | openssl x509 -noout -text \
  | grep -E "Subject:|DNS:|Not After"

-servername sets the SNI name the controller uses to pick a certificate. Look for your host among the DNS: names, which are the certificate's subject alternative names. If it isn't there, the controller isn't using your Secret. A past Not After date means the certificate has expired. Controllers handle a bad Secret differently. F5's controller breaks TLS for the affected hosts. ingress-nginx answered unmatched names with a self-signed default certificate unless you set your own. Its common name is Kubernetes Ingress Controller Fake Certificate, so that name in the openssl output means ingress-nginx served its default instead of your certificate.

Annotations not working

Annotations belong to one controller, and one your controller doesn't support has no effect. A rewrite-target copied from ingress-nginx can therefore pass unrewritten paths to your app, which then answers 404. Invalid values can also make a controller skip the whole Ingress, as F5's does.

DNS doesn't point at the controller

Compare the name's DNS answer with the Ingress address:

dig +short shop.example.com
kubectl get ingress shop -n shop

The name should resolve to the ADDRESS, directly or through a CNAME to the load balancer's hostname. ExternalDNS can create these records from your Ingress hosts.


We have run production systems since 2009. For a second opinion on your Ingress setup, our Kubernetes consulting team starts with read-only access and a written list of what to fix first. If you'd rather not run Ingress yourself, our managed Kubernetes service keeps Ingress controllers and TLS certificates working as the cluster changes.

Frequently Asked Questions

Is Ingress an API gateway?

Not by itself. Ingress is a routing API that matches hosts and paths for HTTP and HTTPS and terminates TLS. An API gateway, as the Gateway API docs describe it, also centralizes authentication and request limits for API consumers. Some Ingress controllers come from API gateway products, such as Kong, Gloo and Emissary-Ingress, and offer more of those features.

Is the Gateway API replacing Ingress?

Gateway API is the successor the Kubernetes project recommends, and new features land only there. Ingress stays, though. The Ingress API remains GA and frozen, and the Gateway API FAQ says there are no plans to deprecate it. It also expects most controllers to keep supporting Ingress indefinitely. Plan new work on Gateway API.

Which ingress controller is best for Kubernetes?

None is best for every cluster. On one cloud, the provider's controller gives you a managed load balancer. Across clouds or on bare metal, pick a portable controller built on a proxy your team knows, such as Envoy, NGINX, HAProxy or Traefik. Avoid ingress-nginx, which gets no security fixes. For new builds, consider Gateway API.

What is an Ingress manifest file?

An Ingress manifest is a YAML or JSON file that describes an Ingress object for kubectl apply. It sets apiVersion: networking.k8s.io/v1, kind: Ingress, metadata and a spec. The spec holds ingressClassName, rules with hosts and paths, each path's pathType and backend Service, and optional tls and defaultBackend sections.

What are the types of Ingress in Kubernetes?

The Kubernetes docs show three routing patterns. An Ingress backed by a single Service has only a default backend. A simple fanout routes several paths on one host to different Services. Name-based virtual hosting routes several hostnames on one IP address to different Services. The same section adds TLS from a Secret, which works with any of them.

Why was ingress-nginx retired?

It lacked the maintainers to keep it secure. For years, one or two people maintained it in their spare time. Its snippet annotations, which inject raw NGINX configuration, turned into a security risk. In March 2025, flaws led by CVE-2025-1974, rated 9.8 CVSS, could let anything on the pod network take over a cluster. Retirement followed in March 2026.