MakeStack
Back to Feed

Use Kubernetes Gateway API instead of ingress-nginx with Cilium

Expose Kubernetes HTTP workloads with Cilium Gateway API resources instead of installing ingress-nginx, using GatewayClass, Gateway, HTTPRoute, and Cilium Envoy.

Why Gateway API with Cilium#

A common Kubernetes setup installs Cilium for pod networking and then installs ingress-nginx separately for HTTP ingress. That works, but it adds another controller, another public Service, another configuration model, and another upgrade path.

If Cilium is already the networking layer, Cilium Gateway API lets the cluster expose north-south HTTP traffic through Gateway API resources instead. The Cilium operator watches Gateway API objects, validates them, and programs Cilium Envoy through the Cilium datapath.

Use Gateway API when you want the ingress path to be part of the Cilium networking stack instead of a separate ingress-nginx controller.

Cilium Gateway API deployment rule
Cilium Gateway API documentation

Official Cilium docs covering prerequisites, GatewayClass support, exposure modes, verification, and troubleshooting.

Cilium migration notes from Ingress to Gateway

Cilium's migration notes explain why Gateway API replaces annotation-heavy Ingress patterns with Gateway and Route resources.

Starting point#

This guide assumes the cluster already has Cilium installed with Gateway API support enabled. In the previous Cilium guide, that was done by installing Gateway API CRDs first and installing Cilium with kube-proxy replacement and gatewayAPI.enabled=true.

Read the Cilium installation guide

Use this first if Cilium, Gateway API CRDs, kube-proxy replacement, or the cilium GatewayClass are not ready yet.

Examplesh
kubectl get gatewayclass
Examplesh
kubectl get crd \
  gatewayclasses.gateway.networking.k8s.io \
  gateways.gateway.networking.k8s.io \
  httproutes.gateway.networking.k8s.io
Examplesh
cilium status
expected-gatewayclass.txttext
NAME     CONTROLLER                     ACCEPTED
cilium   io.cilium/gateway-controller   True

Do not install ingress-nginx for this path. The resources you create are Gateway API resources, not networking.k8s.io Ingress resources, and the controller that reconciles them is Cilium.

Deploy a demo service#

Create a small namespace, Deployment, and Service. Gateway API routes traffic to Kubernetes Services, so the backend must exist before the HTTPRoute can resolve cleanly.

Examplesh
kubectl create namespace gateway-demo
Examplesh
kubectl create deployment web \
  --namespace gateway-demo \
  --image=nginx:1.29
Examplesh
kubectl expose deployment web \
  --namespace gateway-demo \
  --port=80 \
  --target-port=80
Examplesh
kubectl get pods,svc -n gateway-demo
expected-demo-service.txttext
deployment.apps/web
service/web

Create the Gateway and HTTPRoute#

The Gateway is the listener owned by the platform side of the cluster. The HTTPRoute is the application routing rule that attaches to that Gateway and forwards matching requests to the web Service.

gateway-demo.yamlyaml
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: public-http
  namespace: gateway-demo
spec:
  gatewayClassName: cilium
  listeners:
    - name: http
      protocol: HTTP
      port: 80
      allowedRoutes:
        namespaces:
          from: Same
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: web
  namespace: gateway-demo
spec:
  parentRefs:
    - name: public-http
  hostnames:
    - web.example.com
  rules:
    - matches:
        - path:
            type: PathPrefix
            value: /
      backendRefs:
        - name: web
          port: 80
Examplesh
kubectl apply -f gateway-demo.yaml

The important replacement for ingress-nginx is gatewayClassName: cilium. You are not creating an IngressClass named nginx, and you are not depending on nginx-specific annotations.

Verify the Gateway#

Check that Cilium accepted and programmed the Gateway, then inspect the route. A programmed Gateway means Cilium has prepared the Envoy configuration for that Gateway.

Examplesh
kubectl get gateway -n gateway-demo
Examplesh
kubectl describe gateway public-http -n gateway-demo
Examplesh
kubectl get httproute -n gateway-demo
Examplesh
kubectl describe httproute web -n gateway-demo
expected-gateway-state.txttext
NAME          CLASS    ADDRESS          PROGRAMMED
public-http   cilium   <ip-or-hostname>  True

If ADDRESS stays empty, the Gateway object may still be valid, but the cluster does not have a working exposure path for the generated Gateway Service. On a cloud cluster this is usually a LoadBalancer integration. On a self-hosted cluster, use an explicit load balancer implementation, Cilium LB IPAM with L2/BGP announcement, NodePort, or Cilium Gateway API host-network mode.

Examplesh
kubectl get service -n gateway-demo

Test HTTP routing#

Once the Gateway has an address, send an HTTP request through the Gateway. Because the HTTPRoute matches web.example.com, include the Host header until real DNS points at the Gateway address.

Examplesh
GATEWAY=$(kubectl get gateway public-http \
  -n gateway-demo \
  -o jsonpath='{.status.addresses[0].value}')

curl -i \
  -H "Host: web.example.com" \
  "http://$GATEWAY/"
expected-http-response.txttext
HTTP/1.1 200 OK

When DNS is ready, point web.example.com at the Gateway address and call the hostname directly instead of passing the Host header manually.

Translate an ingress to Gateway API#

A basic ingress-nginx resource usually combines public listener selection and application routing in one Ingress object. Gateway API splits that into a Gateway and one or more Routes.

Old ingress-nginx style#

ingress-nginx-web.yamlyaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: web
  namespace: gateway-demo
spec:
  ingressClassName: nginx
  rules:
    - host: web.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: web
                port:
                  number: 80

New Gateway API style#

httproute-web.yamlyaml
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: web
  namespace: gateway-demo
spec:
  parentRefs:
    - name: public-http
  hostnames:
    - web.example.com
  rules:
    - matches:
        - path:
            type: PathPrefix
            value: /
      backendRefs:
        - name: web
          port: 80

Do not copy nginx-specific annotations across blindly. Gateway API has native fields and filters for common routing features, while implementation-specific behavior should be reviewed deliberately before migration.

HTTPRoute API reference

Official HTTPRoute reference for parentRefs, hostnames, matches, filters, and backendRefs.

Troubleshooting#

Start with Gateway and HTTPRoute status conditions. They usually show whether the object was accepted, whether backend references resolved, and whether Cilium programmed the Gateway.

Examplesh
kubectl describe gateway public-http -n gateway-demo
Examplesh
kubectl describe httproute web -n gateway-demo
Examplesh
kubectl logs -n kube-system deploy/cilium-operator
Examplesh
cilium status
gateway-api-checklist.txttext
GatewayClass cilium is accepted
Gateway uses gatewayClassName: cilium
Gateway shows PROGRAMMED True
HTTPRoute shows Accepted True
HTTPRoute shows ResolvedRefs True
Gateway has an address or a deliberate exposure mode
Backend Service name and port match the HTTPRoute backendRefs

The most common failures are missing Gateway API CRDs, Gateway API not enabled in Cilium, a Gateway using the wrong class, an HTTPRoute pointing to a missing Service, or a valid Gateway with no LoadBalancer address in a local cluster.

Cleanup#

Remove the demo Gateway API resources and namespace when the test is finished.

Examplesh
kubectl delete -f gateway-demo.yaml && \
kubectl delete namespace gateway-demo