Skip to main content
DevOps Insightsguides & answers

Kubernetes · commonly confused

Ingress vs Gateway API

Short answer

Ingress is not deprecated. The Kubernetes documentation calls it frozen, which means it stays generally available with no plans for removal but will never gain another feature. Gateway API is its successor, and because Gateway API has no Ingress kind, moving across is a one-time conversion rather than an upgrade.

Reproduced on Gateway API v1.6.3, . Where the text says “reproduced”, the behaviour was run on that version, not taken from documentation.

First published

On this page · 10 sections

In simple terms

Both answer the same question: how does a request from outside the cluster reach a Service inside it. Ingress was the original answer and it is still a working one — Kubernetes marks it stable since v1.19 and states that the project has no plans to remove it.

What the documentation does say is that the Ingress API has been frozen, and that word is doing specific work. Frozen is not deprecated and it is not broken. It means the API is finished: it keeps the stability guarantees of any generally available API, and it will have no further changes or updates made to it. Everything new happens somewhere else, and that somewhere else is Gateway API. Alongside that, the project does now recommend using Gateway instead of Ingress.

The reason it was frozen rather than extended is that Ingress ran out of room. The spec itself expresses little more than host, path and TLS termination. Anything past that — matching on a header, splitting traffic by weight for a canary — was only possible, in the documentation's own words, by using custom annotations. Those annotations never became part of the API, so the annotated parts of an Ingress written for one controller may not work at all under another, which quietly made a portable-looking resource non-portable.

The practical surprise for anyone expecting a drop-in replacement is that Gateway API is not built into Kubernetes. It is an add-on, shipped as Custom Resources, so a brand-new cluster has no Gateway kind at all until somebody installs the CRDs, and nothing acts on a Gateway until a controller that implements them is installed too. Ingress needs a controller too, but the Ingress type itself is always present.

Words used on this page

Ingress
The built-in API object (networking.k8s.io/v1) that routes outside HTTP and HTTPS traffic to Services by host and path. Stable since Kubernetes v1.19, and now frozen.
Ingress controller
The program that reads Ingress objects and configures a proxy or load balancer to match. The IngressClass named in ingressClassName says which controller handles an Ingress.
CRD (CustomResourceDefinition)
A way to add new kinds of object to a cluster's API. Gateway API's kinds are CRDs, installed separately from Kubernetes.
GatewayClass
A kind of Gateway, and the controllerName of the implementation that manages Gateways of that class. In the project's role model, the infrastructure provider manages it.
Gateway
One instance of traffic-handling infrastructure, such as a cloud load balancer or an in-cluster proxy, with the listeners it accepts traffic on.
listener
One entry on a Gateway: a port, a protocol, optionally a hostname and TLS settings, and the rules for which routes may attach.
HTTPRoute
Rules that match HTTP requests arriving on a Gateway's listener, by host, path, headers and more, and send them to backends such as Services.
allowedRoutes
The listener setting that decides which namespaces' routes may attach. If you leave it out, only routes in the Gateway's own namespace can.
ReferenceGrant
An object in a target namespace that allows references into it from another namespace, such as a route in prod sending traffic to a Service here.
release channel
Standard, for resources and fields that have graduated, or experimental, which adds ones that can still change in breaking ways or be removed.

What actually happens

Gateway API's core kinds are stable and served under gateway.networking.k8s.io/v1: GatewayClass, Gateway and HTTPRoute, with GRPCRoute alongside them. As of Gateway API v1.6.3 the standard channel holds ten kinds served under v1: those four plus ReferenceGrant, BackendTLSPolicy, TLSRoute, ListenerSet, TCPRoute and UDPRoute. Check the project's own API reference for the current list; kubernetes.io's summary lags behind it, and so does the project's own getting-started page. A Gateway describes an instance of traffic-handling infrastructure, such as a cloud load balancer or an in-cluster proxy. Routes map traffic from a Gateway listener to backend network endpoints, which are usually a Service — an implementation may represent that backend as the Service IP or as the Service's backing EndpointSlices.

Where Ingress is one object, Gateway API is several, and the split follows organizational roles rather than convenience. The documentation names three: the infrastructure provider, the cluster operator, and the application developer who is concerned with application-level configuration. That is why listeners, ports and TLS live on the Gateway while path and header matching lives on a route — the two are usually edited by different people with different permissions, and a single Ingress object forces them to share one.

A Gateway is associated with exactly one GatewayClass, and the GatewayClass carries the controllerName of the controller that manages Gateways of that class. Routes then attach to Gateways, and a Gateway can filter which routes may attach to its listeners, forming what the documentation calls a bidirectional trust model. The default side of that trust is the one that catches people: by default a Gateway only accepts Routes from the same namespace, and crossing a namespace boundary requires configuring allowedRoutes.

Stability is per-feature, not per-product, which matters when an implementation advertises Gateway API support. The project ships two release channels — standard, holding fields and resources that have graduated to beta or GA (new ones now go straight to v1), and experimental, which adds fields and resources that may still be changed in breaking ways or removed altogether. Independently of the channel, each feature carries a support level: Core, which every implementation is expected to work toward, Extended, which is portable but not universally supported, and implementation-specific, which is vendor-specific and is not covered by conformance tests at all.

The same kind of routing, expressed both ways

# --- Ingress: one object, and host plus path is most of what it says. ---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: minimal-ingress
spec:
  ingressClassName: nginx-example
  rules:
  - http:
      paths:
      - path: /testpath
        pathType: Prefix
        backend:
          service:
            name: test
            port:
              number: 80

# --- Gateway API: the same job, split by who owns which half. ---
# Cluster-operator side: where traffic arrives, and who may attach to it.
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: example-gateway
  namespace: example-namespace
spec:
  gatewayClassName: example-class
  listeners:
  - name: http
    protocol: HTTP
    port: 80
    hostname: "www.example.com"
    allowedRoutes:
      namespaces:
        from: Same        # the default, and the one that surprises people

# Application-developer side: where traffic goes once it is in.
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: example-httproute
spec:
  parentRefs:
  - name: example-gateway
  hostnames:
  - "www.example.com"
  rules:
  - matches:
    - path:
        type: PathPrefix
        value: /login
    backendRefs:
    - name: example-svc
      port: 8080

Both Gateway API objects are taken from the Kubernetes documentation's own examples. Note what is missing from the Gateway: with no addresses field, an address or hostname is assigned by the implementation's controller rather than by you.

Try it yourself

Before you start

  • For the example: curl, grep and network access to github.com. It reads Gateway API's release file and needs no cluster.
  • To try Gateway API in a cluster: rights to install CustomResourceDefinitions, which are cluster-wide. The project's CRD guide says a cluster admin or the cluster provider should own them; kubectl auth can-i create customresourcedefinitions prints yes only if you can.
  • A supported Kubernetes version. The project says each Gateway API release supports the five most recent Kubernetes minor versions; check kubectl version against the release you install.
  • An implementation, chosen from the project's implementations list, and its documentation on which channel and features it supports. Without one, Gateway objects are accepted and nothing happens.
  • For Ingress, an Ingress controller: kubectl get ingressclass lists the classes your cluster offers. Creating an Ingress with no controller has no effect.

What installing Gateway API actually adds

Reproduced on Gateway API v1.6.3 on 12 Oct 2026: its standard-channel release file, downloaded with curl 8.7.1 and read with the system grep (BSD grep 2.6.0) on macOS, in a scratch folder with the time zone set to UTC. No cluster is involved. The counts from uniq -c are padded with spaces, and GNU uniq pads them differently.

Command

curl -sLO https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.6.3/standard-install.yaml
grep -E '^kind:' standard-install.yaml | sort | uniq -c
grep -E '^    kind: ' standard-install.yaml

Output

$ curl -sLO https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.6.3/standard-install.yaml

$ grep -E '^kind:' standard-install.yaml | sort | uniq -c
  10 kind: CustomResourceDefinition
   1 kind: ValidatingAdmissionPolicy
   1 kind: ValidatingAdmissionPolicyBinding

$ grep -E '^    kind: ' standard-install.yaml
    kind: BackendTLSPolicy
    kind: GatewayClass
    kind: Gateway
    kind: GRPCRoute
    kind: HTTPRoute
    kind: ListenerSet
    kind: ReferenceGrant
    kind: TCPRoute
    kind: TLSRoute
    kind: UDPRoute

How to read it

  • curl printed nothing: -s silences it, -L follows GitHub's redirect to the file, and -O saves it under its own name. -s also hides errors, so if the next commands print nothing, check the download first.
  • The first grep counts the objects in the file: ten CustomResourceDefinitions, plus an admission policy and its binding. That is all this file adds: new kinds for the API server, a guard policy, and no controller. Nothing acts on a Gateway until you also install an implementation.
  • The second grep lists the ten kinds the standard channel defines in v1.6.3. kubernetes.io's Gateway page names four stable kinds, while the project's own API reference marks all ten as GA. Read the status from the project's reference, not from summaries.
  • Ingress is not in the file and does not need to be: it is built into Kubernetes under networking.k8s.io/v1. Gateway API has no Ingress kind, which is why moving across means converting your objects rather than upgrading them.
  • The ValidatingAdmissionPolicy is a guard the project ships. It refuses experimental-channel CRDs over standard ones, and Gateway API CRDs from older releases, until someone removes it. Applying this file needs cluster-admin rights, because CRDs and admission policies are cluster-wide.

The mistake this causes

Putting the Gateway in one namespace and the routes in another, the way a single Ingress controller serves Ingress objects from everywhere, without configuring allowedRoutes.

Why people do it It is the Ingress mental model carried across intact. Under Ingress, the resource lives in the application's namespace and a cluster-wide controller picks it up, so namespaces never enter the conversation. Gateway API inverts the consent: the Gateway decides which routes may attach to it, and the shipped default is same-namespace only.

What you see Nothing errors. The route is valid, kubectl accepts it, and the object sits there looking correct. Requests simply arrive at the gateway and come back as a 404 generated by the proxy rather than by your application, so the application logs stay silent and show no request at all. The refusal is recorded on the route's own status, which is the one place nobody looks first.

If it goes wrong

kubectl get gateways prints error: the server doesn't have a resource type "gateways"
The Gateway API CRDs are not installed, which is the normal state of a new cluster. Ask whoever administers the cluster to install a standard-channel release, or follow your chosen implementation's install guide, which may include them.
kubectl apply fails with no matches for kind "Gateway" in version "gateway.networking.k8s.io/v1" and ensure CRDs are installed first
Same cause: the API server does not know the kind yet. Install the CRDs first, then apply your Gateway and routes.
Every request returns 404, and the HTTPRoute's status shows condition Accepted with status False and reason NotAllowedByListeners
The Gateway's listener does not allow routes from the route's namespace, and by default it allows only its own. Change allowedRoutes on the Gateway's listener, not the route: from: Selector with a selector on kubernetes.io/metadata.name admits named namespaces. kubectl get httproute <name> -n <namespace> -o yaml shows the status.
The HTTPRoute has no status at all
Its parentRefs does not point at a Gateway any installed implementation owns, so no controller may write status to it. Check the Gateway's name, and set namespace in the parentRef when the Gateway is elsewhere: it defaults to the route's own namespace.
Installing the experimental channel fails with Installing experimental CRDs on top of standard channel CRDs is prohibited by default.
This is the project's safe-upgrades.gateway.networking.k8s.io admission policy doing its job. Experimental CRDs can change or drop fields without deprecation, so decide deliberately. The message says the policy must be uninstalled to go ahead; that is a cluster-admin decision.

How it shows up in production

A team decides to try Gateway API for one new service while leaving everything else on Ingress. A platform engineer installs the CRDs and a controller, creates a GatewayClass and a Gateway in a shared namespace such as the one holding the ingress tier, and tells the application teams to point routes at it.

The first team writes an HTTPRoute in their own namespace with a parentRefs entry naming the Gateway and its namespace. It applies cleanly. The hostname resolves, TLS terminates, and every request returns 404.

The investigation starts at the application, because a 404 reads like a routing bug in the service. Nothing there is wrong, and the giveaway is that the application never logs the request — the 404 is being produced upstream, before anything reached the pod.

The Gateway was never willing to adopt a route from another namespace, because nobody set allowedRoutes and the default is Same. The fix is a one-line change on the Gateway, not on the route, which is the part that feels backwards to anyone coming from Ingress: the object that needs editing belongs to a different team than the object that appears broken.

How to tell which one you are hitting

Do I have to migrate off Ingress?
No. Kubernetes states plainly that it has no plans to remove Ingress and that the API keeps its generally-available stability guarantees. What you lose by staying is future work, because frozen means no further changes or updates will be made to it. Treat it as a decision about new services rather than an evacuation.
Is Gateway API already available in my cluster?
Not in upstream Kubernetes by default, though some managed offerings install it for you, so check your provider. It is an add-on rather than a built-in API, so the kinds only exist after the CRDs are installed, and only do anything once an implementation is installed too. If kubectl reports that a Gateway resource is not recognised, that is the expected state of an untouched cluster, not a misconfiguration.
My implementation says it supports Gateway API. Does everything in the docs work?
Not necessarily, and this is worth checking before designing around a feature. Support is defined per feature, not per product: Core features should be portable, Extended features are portable but not universally supported, and implementation-specific ones are vendor-specific and carry no conformance tests. Anything from the experimental channel can still change in breaking ways or be removed.
Can I convert my Ingress objects automatically?
Not within the API itself: Gateway API has no Ingress kind, so the move is a one-time conversion. The project's migration guide points to the ingress2gateway tool, which converts Ingress resources into Gateway and HTTPRoute resources; test what it produces. It handles only some controller-specific annotations, so expect to carry the rest over by hand, since those were never part of the Ingress spec in the first place.

Security considerations

  • When a listener admits routes with from: Selector, select namespaces by the kubernetes.io/metadata.name label. The project's security guide warns that with a custom label such as env, anyone who can label namespaces can add their namespace to your Gateway.
  • Routes from different namespaces can claim the same hostname, and the project's security guide says implementations usually settle that first come, first served. A team that can attach routes can take over another team's hostname by adding it to an older route. Give each hostname its own listener, admitting only the namespace that owns it.
  • CRDs are cluster-wide and highly privileged, so only cluster admins or the cluster provider should install or upgrade Gateway API. Implementations that bundle the CRDs must never overwrite newer versions, switch channels or remove them; check that yours follows this before letting it manage them.
  • A ReferenceGrant lets objects in another namespace use yours, for example Gateways there using a TLS certificate Secret here. It trusts a kind and a namespace, never named objects, so anyone who can create that kind there gets the access, and without a name under to it covers every Secret or Service of that kind here. Name the exact object, and grant only the namespaces that need it.
  • Moving an Ingress to another controller, or to Gateway API, can drop protections that were set through controller-specific annotations, such as authentication or IP allow-lists. The other implementation may not support them at all. List every annotation and confirm its replacement before switching traffic.

In an interview

How this gets asked in an interview

Usually phrased as Is Ingress deprecated, and what does Gateway API do differently?

What a strong answer contains The useful answer starts by refusing the premise: Ingress is frozen, not deprecated, and the distinction is that it remains generally available with no removal planned while receiving no further features. On the difference, the strongest signal is naming the role split rather than listing features — Gateway API breaks one object into GatewayClass, Gateway and routes because listeners and TLS are operated by different people than path matching. Mentioning that Gateway API is an add-on installed as CRDs, not a built-in API, usually indicates someone has actually stood it up.

The follow-up "You have a Gateway and an HTTPRoute in different namespaces, the route applies cleanly, and every request 404s. What happened?" The Gateway defaults to accepting routes from its own namespace only, so the route was never adopted and allowedRoutes was never configured. Worth adding that the fix belongs on the Gateway, which is the inversion of control Ingress never had.

Sources

The behaviour this page describes, as documented by the projects themselves.

Did this get you to an answer?

No text box on purpose — please do not paste production logs anywhere

Something wrong or out of date? Report it