Lesson 30 of 66 · 16 min read · Updated
From Ingress to Gateway API — Why Kubernetes Is Moving On
On this page
In the Services series you exposed kiosk-api to the outside world with NodePort and LoadBalancer Services. Both work, but both operate at layer 4. They forward TCP connections and know nothing about hostnames, paths, headers, or TLS certificates. A real platform needs one entry point that looks at an HTTP request and decides which Service should answer it.
For almost ten years that entry point was Ingress. It is still in nearly every cluster, nearly every tutorial, and nearly every Helm chart. It is also the past. The most popular Ingress controller, Ingress NGINX, has been retired, and the Kubernetes project now points everyone to Gateway API instead.
This article opens a new series. Before you install anything new, you need to understand what you are moving away from, why the move is happening, and what in your cluster will have to change.
We will cover the following topics:
- What Ingress is: the resource, the controller, and the IngressClass that connects them
- A complete Ingress for kiosk-api, read line by line
- The structural limits of Ingress and why annotations became a problem
- The Ingress NGINX retirement and what it does and does not mean
- Gateway API as the successor, and its role-oriented design
- How every Ingress concept maps to a Gateway API resource
- The main Gateway API implementations you can choose from
- How to audit a cluster so you know exactly what must migrate
Technical Requirements
- A working Kubernetes cluster and
kubectl. The examples use minikube. - The
kiosk-api:1.0image from the Services series. The Step-by-Step section shows how to rebuild it if you do not have it. jqfor the audit commands.- Optionally the minikube
ingressaddon, which installs the Ingress NGINX controller. You only need it if you want to see the Ingress actually serve traffic.
Core Concepts
What Ingress Is
Ingress is two things that people usually blur into one.
The first is the Ingress resource, an object in the networking.k8s.io/v1 API. It is pure configuration. It says "requests for this host and this path should go to this Service on this port". On its own it does nothing at all.
The second is an Ingress controller, a reverse proxy running in the cluster that watches Ingress resources and turns them into its own routing configuration. Ingress NGINX, Traefik, HAProxy, and Contour are all Ingress controllers. Kubernetes does not ship one. If no controller is installed, you can create as many Ingress objects as you like and no request will ever reach your Pods.
The link between the two is the IngressClass. A cluster can run several controllers at once, so each Ingress names the class it belongs to with spec.ingressClassName, and each controller only processes the Ingresses of its own class.
| Piece | Kind of thing | Who creates it |
|---|---|---|
| Ingress controller | A Deployment running a proxy | Cluster operator, usually with Helm |
| IngressClass | Cluster-scoped API object | Installed with the controller |
| Ingress | Namespaced API object | Application developer |
An Ingress for kiosk-api
Here is a typical Ingress. It sends storefront.local/kiosk to the kiosk-api Service and strips the prefix so the .NET app sees /.
Read it from the bottom up. The backend is a Service and a port, exactly what you built in the Services series. The path and host decide which requests match. The ingressClassName decides which controller cares about this object.
Then look at the annotations. The path is a regular expression, and the rewrite uses a capture group. Neither of those exists in the Ingress specification. They are features of one specific controller, smuggled in through free-form strings. That is the heart of the problem with Ingress.
The Limits of Ingress
Ingress was designed in 2015 for a simple job: host and path routing for HTTP. It reached v1 in Kubernetes 1.19 and has barely changed since. Everything people needed beyond that simple job ended up in annotations.
| Limit | What it means in practice |
|---|---|
| Tiny core spec | Only host, path, backend, and TLS are standard. Rewrites, redirects, timeouts, CORS, rate limits, and header matching are not. |
| Annotation sprawl | Each controller invented its own annotations. Ingress NGINX alone has more than a hundred. They are untyped strings, so a typo is silently ignored. |
| Not portable | An Ingress full of nginx.ingress.kubernetes.io/* annotations means nothing to Traefik or to a cloud load balancer. Switching controllers means rewriting every object. |
| One role, one object | The same object holds the hostname, the TLS certificate, and the routing rules. Platform teams and application teams fight over who owns it. |
| HTTP only | There is no standard way to route TCP, UDP, gRPC, or TLS passthrough. |
| No traffic splitting | Weighted canaries need yet more annotations, or a second "canary Ingress", depending on the controller. |
| Weak status | An Ingress tells you almost nothing about whether the controller accepted it, or why not. |
The annotations were also a security problem. Some Ingress NGINX annotations accepted raw NGINX configuration snippets. Anyone who could create an Ingress could inject configuration into a proxy shared by the whole cluster. Several serious CVEs, including the 2025 "IngressNightmare" vulnerabilities, came from exactly that design.
The Ingress NGINX Retirement
In November 2025 Kubernetes SIG Network and the Security Response Committee announced the retirement of the Ingress NGINX controller. The project received best-effort maintenance until March 2026. Since then there are no new releases, no bug fixes, and no security patches.
It is important to be precise about what this means:
- Existing installations keep working. Nothing is switched off. The images and Helm charts are still downloadable.
- Every new vulnerability stays open. An internet-facing proxy that never gets patched is a liability that grows every month.
- Only the controller is retired. The
kubernetes/ingress-nginxproject is gone. The separate NGINX Ingress Controller made by F5/NGINX is a different product and is not affected. - The Ingress API is not removed. The
Ingresskind remains in Kubernetes with no announced deprecation, but it is feature-frozen. It will not gain new capabilities.
The official recommendation is to migrate to Gateway API. If you must stay on Ingress for a while, move to another maintained Ingress controller. Either way, planning the migration starts with knowing what you have, which is what the rest of this article is about.
Gateway API, the Successor
Gateway API is a set of Kubernetes APIs developed by SIG Network as the next generation of Ingress. It is not built into the API server. You install it as Custom Resource Definitions, usually bundled with the implementation you choose. Its core resources reached v1 (GA) in October 2023, and each release since then has promoted more features into the stable Standard channel. Release v1.5 in February 2026 moved ListenerSet, TLSRoute, ReferenceGrant, and the HTTPRoute CORS filter into Standard. The current release line is v1.6.
The biggest change is that Gateway API splits the old Ingress object into several resources, each owned by a different role:
| Role | Typical owner | Resource they manage |
|---|---|---|
| Infrastructure provider | Cloud vendor or the team running the proxy software | GatewayClass |
| Cluster operator | Platform team | Gateway (ports, hostnames, TLS, who may attach) |
| Application developer | The team owning kiosk-api | HTTPRoute, GRPCRoute, and other routes |
The platform team decides that the cluster listens on port 443 for *.storefront.local with a given certificate. Application teams then attach their own routes to that Gateway from their own namespaces, without ever touching the shared object. Kubernetes RBAC can enforce the split, because each role edits a different kind.
Other differences matter just as much:
- Expressive core. Header, query parameter, and method matching, redirects, rewrites, header modification, request mirroring, and weighted traffic splitting are all typed fields, not annotations.
- Protocol aware. Besides
HTTPRoutethere areGRPCRoute,TLSRoute, and experimentalTCPRouteandUDPRoute. - Portable. A route that only uses Standard features behaves the same on every conformant implementation.
- Rich status. Every Gateway and Route reports conditions such as
Accepted,Programmed, andResolvedRefs, with a reason when something is wrong. - Mesh support. The same route types can describe traffic between Services inside a service mesh, not just traffic entering the cluster.
Mapping Ingress to Gateway API
Every Ingress concept has a home in Gateway API, which makes migration mostly mechanical.
| Ingress | Gateway API |
|---|---|
| Ingress controller installation | An implementation, such as Envoy Gateway, plus its GatewayClass |
IngressClass | GatewayClass |
| The controller's shared listening ports | Gateway with one or more listeners |
spec.rules[].host | HTTPRoute.spec.hostnames, optionally restricted by the listener hostname |
spec.rules[].http.paths[] | HTTPRoute.spec.rules[].matches[].path |
pathType: Prefix / Exact | type: PathPrefix / Exact |
backend.service | HTTPRoute.spec.rules[].backendRefs[] |
spec.tls | An HTTPS listener on the Gateway with certificateRefs |
spec.defaultBackend | A rule without matches, which matches everything |
| Rewrite annotations | URLRewrite filter |
| Redirect annotations | RequestRedirect filter |
| Canary annotations | Several backendRefs with weight |
| Header-based routing annotations | matches[].headers |
The kiosk-api Ingress above becomes one Gateway, owned by the platform team, and one HTTPRoute, owned by you. You will write both in the next article.
Choosing an Implementation
Gateway API is only a specification. Something must implement it. The conformance reports on the Gateway API website list which features each implementation supports.
| Implementation | Notes |
|---|---|
| Envoy Gateway | CNCF Envoy project's own controller. Built for Gateway API from day one. Used throughout this series. |
| NGINX Gateway Fabric | NGINX-based implementation from F5/NGINX. A natural landing place for Ingress NGINX users. |
| Istio | Uses Gateway API for both ingress and mesh traffic. |
| Cilium | Implements Gateway API inside its eBPF-based CNI. |
| Traefik | Supports Gateway API alongside its own Ingress support. |
| kgateway, Kong, HAProxy, Contour | Other Envoy- or proxy-based options. |
| Cloud controllers | GKE Gateway controller, AWS Load Balancer Controller and VPC Lattice, Azure Application Gateway for Containers. |
The resources you write stay the same whichever you choose. Only the GatewayClass and implementation-specific extensions change.
Hands-On: Kubernetes Commands
List the IngressClasses in the cluster
Each class is backed by a controller. The CONTROLLER column tells you which one.
List every Ingress in every namespace
This is your migration inventory. The CLASS column shows which controller serves each one.
Check whether Ingress NGINX is running
This is the check recommended in the official retirement announcement. Any result means you are affected.
Find every Ingress NGINX annotation in use
Annotations are the hard part of a migration, because each one needs a Gateway API equivalent.
Look for configuration snippets
Snippets inject raw NGINX configuration. They have no portable equivalent and need manual review.
Check whether the Gateway API CRDs are installed
No output means Gateway API is not installed yet.
List the Gateway API kinds the cluster knows
Useful to see which channel and version of the CRDs is present.
Step-by-Step Example
The Scenario
The storefront team runs kiosk-api behind Ingress NGINX. The platform team has read the retirement announcement and wants an inventory before choosing a migration date. You will deploy kiosk-api with a typical Ingress, look at it the way the controller does, and produce the list of things that must change.
Step 1 — Namespace and Application
If you still have the storefront namespace and the kiosk-api Deployment and Service from the Services series, skip to Step 2. Otherwise, build the image inside minikube's Docker daemon so the cluster can use it without a registry:
Replace Program.cs:
Add a Dockerfile and build:
On Docker Desktop's built-in Kubernetes with the kind provisioner, skip eval $(minikube docker-env): images you build with docker build are available to the cluster nodes directly. On a plain kind cluster, run kind load docker-image kiosk-api:1.0 after the build. In both cases, a node that already has kiosk-api:1.0 keeps using its cached copy under imagePullPolicy: IfNotPresent, so give a rebuilt image a new tag.
Save the namespace, Deployment, and Service as 1-kiosk-api.yaml:
Step 2 — Optionally Install Ingress NGINX
The minikube ingress addon installs the Ingress NGINX controller. Install it only to see the Ingress work end to end. Do not use it for anything new.
On Docker Desktop or kind there is no addon. Install the same controller from its Helm chart; it still downloads, it just gets no updates:
If the first kubectl apply of an Ingress right after the install fails with failed calling webhook "validate.nginx.ingress.kubernetes.io" ... x509: certificate signed by unknown authority, the admission webhook is still being set up. Wait a few seconds and apply again.
If you skip this step, everything below still works. The Ingress object is accepted by the API server, it just has no controller to act on it, which is a useful lesson in itself.
Step 3 — Create the Ingress
Save the Ingress from the Core Concepts section as 2-kiosk-api-ingress.yaml and apply it:
The ADDRESS column is filled in by the controller. Without a controller it stays empty forever.
Step 4 — Send a Request Through the Ingress
With the addon installed, send a request to the minikube IP with the right Host header:
On Docker Desktop's kind cluster the controller's LoadBalancer Service is published on localhost, so the same request is curl -H "Host: storefront.local" http://localhost/kiosk/. The 172.18.x.x address shown in the ADDRESS column is inside Docker's network and not reachable from macOS or Windows.
The app saw /, not /kiosk/. The rewrite annotation did that. Remember it, because the rewrite is exactly the kind of behaviour that has to be translated, not copied, during a migration.
Step 5 — See What the API Server Actually Stored
Describe the Ingress. Look at what Kubernetes understands and what it does not.
The rules are structured data. The annotations are just strings. The API server has no idea that rewrite-target changes the request path. If you mistype it as rewrite-targett, the apply succeeds and the rewrite silently stops working.
Step 6 — Run the Audit
Now produce the inventory a migration needs. First, which controllers and classes exist:
Then every Ingress with its controller-specific annotations:
Finally, check whether Gateway API is already present:
On a fresh minikube this prints nothing. You will fix that in the next article.
Step 7 — Write Down the Migration Plan
For a real cluster you would put the audit output in a table like this one. For kiosk-api it is short:
| Ingress feature | Where it is used | Gateway API equivalent |
|---|---|---|
Host storefront.local | storefront/kiosk-api | HTTPRoute.spec.hostnames |
Regex path under /kiosk | storefront/kiosk-api | PathPrefix match on /kiosk |
rewrite-target: /$2 | storefront/kiosk-api | URLRewrite filter with ReplacePrefixMatch: / |
Class nginx | storefront/kiosk-api | parentRefs to a Gateway of class eg |
Notice the second row. A regex path was only needed because the rewrite needed capture groups. Gateway API can strip a prefix directly, so the regex disappears. Many migrations get simpler, not harder.
Step 8 — Clean Up
Delete the Ingress and, if you enabled it, the addon. Keep the storefront namespace and kiosk-api, because the next article uses them.
If you installed the controller with Helm, use helm uninstall ingress-nginx -n ingress-nginx instead of the addon command.
Summary
- Ingress is a resource plus a controller, linked by an IngressClass. Without a controller an Ingress does nothing.
- The Ingress spec only covers host, path, backend, and TLS. Everything else lives in controller-specific annotations, which are untyped, unportable, and in some cases a security risk.
- The Ingress NGINX controller is retired. Since March 2026 it receives no releases and no security fixes. Existing installs still run, but they are no longer safe to leave in place long term.
- The Ingress API is feature-frozen, not removed.
- Gateway API replaces it with role-oriented resources:
GatewayClassfor the provider,Gatewayfor the platform team, and routes such asHTTPRoutefor application teams. - Rewrites, redirects, header matching, and weighted traffic splitting are typed fields in Gateway API.
- Every Ingress concept maps to a Gateway API resource, so migration starts with an audit of classes, Ingresses, and annotations.
In the next article you will install Envoy Gateway, create your first Gateway and HTTPRoute, and send real traffic to kiosk-api through them.