External Traffic: Gateway API and Where Ingress Stands
The Ingress API is frozen but alive; the ingress-nginx controller is archived and unpatched. Untangling the two, and learning Gateway API.
Read This Before You Follow a Tutorial
This is the area of Kubernetes where existing material is most likely to mislead you, and the stakes are unusually concrete: a very large share of blog posts, courses and Stack Overflow answers tell you to install a controller that no longer receives security patches.
Two separate things are often conflated. Keep them apart:
- The Ingress API — a Kubernetes API object. Frozen, but not deprecated or removed. Your existing Ingress manifests keep working.
- The ingress-nginx controller — one community implementation of that API. Archived and end-of-life.
The API is fine. That specific controller is not.
What "Frozen" Means for Ingress
The Kubernetes documentation states the position directly. It recommends using Gateway instead of Ingress, and explains that the Ingress API has been frozen — meaning it remains generally available with the usual stability guarantees, the project has no plans to remove it, and it is no longer being developed, with no further changes or updates.
So Ingress isn't going away. It simply stopped evolving, which is why anything it can't express natively is done through controller-specific annotations — the sprawl that motivated a replacement.
The Part That Is Urgent
Separately, and much more pressing: the community ingress-nginx controller reached end of life, and its repository was archived read-only on 24 March 2026. It receives no further releases, no bug fixes, and no security patches.
Its own maintainers put it plainly: if you are not already using ingress-nginx, you should not be deploying it, and you should instead identify a Gateway API implementation and use it.
This is why "just install the nginx ingress controller" — the single most repeated instruction in Kubernetes tutorials — is now advice that puts unpatched, internet-facing infrastructure into your cluster. Existing deployments won't suddenly break, and the published images and charts remain available, but nothing will be fixed. Treat it as a migration item, not a default.
Note the name collision that causes real confusion: ingress-nginx (the archived community project, kubernetes/ingress-nginx) is a different thing from F5's NGINX Ingress Controller, which is a separately maintained product. When you read that "nginx ingress is dead," it's the former.
Gateway API
Gateway API is the current standard. It is not part of core Kubernetes — it ships as an add-on of CRDs that you install, then pair with an implementation that actually handles traffic.
Its central design idea is splitting one monolithic object into resources owned by different teams:
| Resource | Owned by | Describes |
|---|---|---|
| GatewayClass | Infrastructure provider | Which controller implements this class of gateway |
| Gateway | Cluster operator | Listeners — ports, protocols, hostnames, TLS |
| HTTPRoute / GRPCRoute | Application developer | Routing rules to backend Services |
This matters operationally: the platform team owns TLS certificates and listener configuration, while application teams attach routes without needing permission to edit shared infrastructure. With Ingress, both concerns lived in one object that everyone had to share.
A Working Example
The Gateway — created once by the platform team:
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: public
namespace: gateway-system
spec:
gatewayClassName: envoy
listeners:
- name: https
protocol: HTTPS
port: 443
hostname: "*.example.com"
tls:
mode: Terminate
certificateRefs:
- name: example-com-tls
allowedRoutes:
namespaces:
from: Selector
selector:
matchLabels:
gateway-access: "true"An HTTPRoute — created by each application team:
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: api
namespace: production
spec:
parentRefs:
- name: public
namespace: gateway-system
hostnames:
- "api.example.com"
rules:
- matches:
- path:
type: PathPrefix
value: /v1
backendRefs:
- name: api
port: 80Note allowedRoutes on the Gateway. The operator controls which namespaces may attach routes — cross-namespace routing is explicit and governed, rather than something that happens implicitly.
Capabilities that needed annotations under Ingress
Traffic splitting by weight — canary releases as a first-class feature:
rules:
- backendRefs:
- name: api-v1
port: 80
weight: 90
- name: api-v2
port: 80
weight: 10Header-based routing:
rules:
- matches:
- headers:
- name: x-canary
value: "true"
backendRefs:
- name: api-v2
port: 80Both were controller-specific annotations under Ingress, which meant your manifests were portable in name only — switching controllers meant rewriting them.
Check yourself
A colleague says 'the Ingress API is deprecated, we need to migrate everything immediately or it will stop working.' What's the accurate correction?
Choosing an Implementation
Gateway API defines the API; something must implement it. Actively maintained options include Envoy Gateway, Istio, Cilium, Traefik and kgateway, alongside cloud-provider implementations.
Questions worth asking when choosing:
- Do you already run a service mesh? If Istio or Cilium is in the cluster, its Gateway implementation is usually the path of least resistance.
- Managed or self-hosted? Cloud implementations provision a real load balancer; in-cluster proxies give more control and more operational surface.
- Which conformance level does it meet? The project publishes conformance reports, and support for the extended features you rely on varies.
Migrating From Ingress
You don't need a big-bang cutover. Both can run side by side:
- Install the Gateway API CRDs and your chosen implementation.
- Create a Gateway alongside the existing Ingress controller.
- Move one low-risk service — create its HTTPRoute, shift DNS, verify, keep the Ingress as a fallback.
- Migrate the rest incrementally.
- Remove the old controller once nothing depends on it.
Tooling exists to convert existing Ingress resources into Gateway API resources, which handles the mechanical translation. It can't convert controller-specific annotations — those encode behaviour that has no generic equivalent, and each one needs a deliberate decision. Expect the annotations to be where the real migration work lives.
TLS and Certificates
Gateways terminate TLS by referencing a Secret containing a certificate. Managing those by hand doesn't scale, so cert-manager is the near-universal answer: it obtains certificates from an issuer such as Let's Encrypt, stores them as Secrets, and renews them before expiry.
tls:
mode: Terminate
certificateRefs:
- name: example-com-tls # a Secret cert-manager maintainsmode: Terminate decrypts at the gateway and forwards plaintext inside the cluster. mode: Passthrough forwards encrypted traffic to the backend, which is what you want when the application must see the original TLS session — mutual TLS, for example.
Check yourself
Why does Gateway API split routing into Gateway and HTTPRoute rather than using one object like Ingress?
What's Next
Traffic reaches your services from outside. The next guide covers restricting traffic inside the cluster — where the default is that every pod can reach every other pod, and where a policy can be accepted by the API server and silently do nothing at all.