Kubernetes users had years of muscle memory around the NGINX Ingress Controller. Now that the project has reached end-of-life, the CNCF Gateway API has become the strategic path forward. Gateway API is not just a drop-in replacement; it gives us first-class APIs for shared gateways, cross-team policy, and vendor portability.
This post covers:
- What the retirement of NGINX Ingress support means for clusters you run today
- Why Gateway API is a safer long-term abstraction for L7 traffic
- A step-by-step manifest to expose both a React/Next.js frontend and a Go/Node API via custom domains
Why the change?
- Project lifecycle: The community-maintained
kubernetes/ingress-nginxcontroller is in maintenance-only mode. Security fixes and new Kubernetes features will lag behind. - API ergonomics: Ingress resources tried to cover every proxy. Gateway API gives us discrete resources (
GatewayClass,Gateway,HTTPRoute,GRPCRoute, etc.) and native conformance tests. - Multi-tenancy: Platform teams can standardize gateway ownership while still delegating routes to app squads without granting cluster-admin rights.
Migration approach I recommend
- Inventory current ingress objects and group them by hostname/cert.
- Deploy a conformant Gateway implementation (Envoy Gateway, Kong, Traefik, GKE Gateway, AWS ALB Controller, etc.).
- Translate ingress rules into
HTTPRouteCRDs. Most YAML is reusable; just split host/path matches. - Bake it into GitOps and include policy guardrails (rate limiting, auth) as separate
PolicyAttachmentresources once you need them.
Example: Frontend + API domains
Assume you have:
frontenddeployment serving a React SPA on port 3000apideployment exposing a REST API on port 8080- DNS names
app.example.comandapi.example.com
Below is a ready-to-apply manifest. Replace placeholders with your certificates and images.
apiVersion: gateway.networking.k8s.io/v1
kind: GatewayClass
metadata:
name: envoy-prod
spec:
controllerName: gateway.envoyproxy.io/gatewayclass-controller
---
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: edge-shared
namespace: gateway-system
spec:
gatewayClassName: envoy-prod
listeners:
- name: https-frontend
protocol: HTTPS
port: 443
hostname: app.example.com
tls:
mode: Terminate
certificateRefs:
- kind: Secret
name: app-example-com-cert
- name: https-api
protocol: HTTPS
port: 443
hostname: api.example.com
tls:
mode: Terminate
certificateRefs:
- kind: Secret
name: api-example-com-cert
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: frontend
namespace: app
spec:
replicas: 3
selector:
matchLabels:
app: frontend
template:
metadata:
labels:
app: frontend
spec:
containers:
- name: frontend
image: ghcr.io/org/frontend:stable
ports:
- containerPort: 3000
---
apiVersion: v1
kind: Service
metadata:
name: frontend
namespace: app
spec:
selector:
app: frontend
ports:
- port: 80
targetPort: 3000
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: api
namespace: app
spec:
replicas: 3
selector:
matchLabels:
app: api
template:
metadata:
labels:
app: api
spec:
containers:
- name: api
image: ghcr.io/org/api:v2
ports:
- containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
name: api
namespace: app
spec:
selector:
app: api
ports:
- port: 80
targetPort: 8080
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: frontend-route
namespace: app
spec:
parentRefs:
- name: edge-shared
namespace: gateway-system
sectionName: https-frontend
hostnames:
- app.example.com
rules:
- matches:
- path:
type: PathPrefix
value: /
backendRefs:
- name: frontend
port: 80
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: api-route
namespace: app
spec:
parentRefs:
- name: edge-shared
namespace: gateway-system
sectionName: https-api
hostnames:
- api.example.com
rules:
- matches:
- path:
type: PathPrefix
value: /
backendRefs:
- name: api
port: 80
Bootstrapping tips
- Install Gateway API CRDs:
kubectl kustomize "github.com/kubernetes-sigs/gateway-api/config/crd?ref=v1.1.0" | kubectl apply -f - - Deploy your controller (Envoy Gateway example):
helm repo add envoy-gateway https://envoyproxy.github.io/gateway && helm install envoy-gw envoy-gateway/envoy-gateway -n gateway-system --create-namespace - Apply the manifest above and check status:
kubectl get gateway,httproute -A - Update DNS records to point
app.example.comandapi.example.comat the load balancer IP/hostname created by your Gateway implementation.
Operational guardrails
- Certificates: Use cert-manager or your cloud TLS product to keep listener secrets renewed.
- Policies: Attach
HTTPRouteFilterentries for rate limiting, JWT auth, or header rewrites without editing each route. - Observability: Gateway API standardizes status conditions—watch
status.conditionsforProgrammedandAcceptedto confirm propagation. - Rollbacks: Keep the old NGINX controller around until every HTTPRoute is tested; you can run both gateways side-by-side during migration.
Final thoughts
Gateway API is not just a replacement task on your backlog; it’s an opportunity to adopt a standards-based data plane that improves platform UX. The sooner you codify your ingress patterns with Gateway API resources, the sooner you can retire brittle, controller-specific annotations and gain consistent routing, policy, and status across clusters.