How to Migrate from Ingress-NGINX to Kubernetes Gateway API in 2026
Step-by-step guide to migrating from Ingress-NGINX to Kubernetes Gateway API. Includes YAML examples, implementation choices, testing strategy, and cutover plan.
Ingress-NGINX has been the default Kubernetes ingress controller for years. It's been reliable, well-documented, and understood by nearly every DevOps engineer. But in 2026, the Kubernetes ecosystem is moving decisively toward the Gateway API, and Ingress-NGINX is entering maintenance mode.
The Kubernetes Gateway API (graduated to GA in Kubernetes 1.29) is not just a replacement for Ingress — it's a complete rethinking of how traffic enters and routes through a Kubernetes cluster. It's more expressive, more portable, and designed for the multi-team, multi-tenant reality of modern Kubernetes.
If you're still running Ingress-NGINX, now is the time to migrate. Here's exactly how to do it.
Why Migrate Now?
Three reasons:
-
Ingress-NGINX maintenance is winding down. The project announced reduced maintenance focus in early 2026. Security patches will continue, but new features have stopped.
-
Gateway API is GA and production-ready. It's no longer experimental. Major implementations (Envoy Gateway, Traefik, Cilium, Istio, NGINX Gateway Fabric) are stable.
-
Gateway API solves real problems that Ingress never could — role-based configuration, cross-namespace routing, traffic splitting, header-based matching, and more — all without vendor-specific annotations.
Understanding the Key Differences
Before we migrate, let's understand what's changing conceptually.
Ingress Model
Ingress resource → Ingress Controller (NGINX) → Backend Services
Everything is in one resource. The Ingress object defines hosts, paths, TLS, and backends all in a single YAML file. Fine for simple cases, but it leads to annotation sprawl for anything complex.
Gateway API Model
GatewayClass → Gateway → HTTPRoute → Backend Services
The configuration is split across three layers:
- GatewayClass: Defines which controller handles the traffic (like StorageClass for volumes). Managed by the infrastructure team.
- Gateway: Defines the actual listener — ports, protocols, TLS certificates. Managed by the cluster/platform team.
- HTTPRoute: Defines routing rules — hosts, paths, headers, backends. Managed by application teams.
This separation is the killer feature. Application teams can manage their own routing without touching gateway infrastructure. No more "please add this annotation to the shared Ingress controller" tickets.
Step 0: Audit Your Current Ingress Resources
Before you change anything, inventory what you have. Run this across your cluster:
# List all Ingress resources
kubectl get ingress -A -o wide
# Export all Ingress resources for reference
kubectl get ingress -A -o yaml > ingress-backup.yaml
# Count unique annotations (these are the tricky part)
kubectl get ingress -A -o json | jq -r '.items[].metadata.annotations // {} | keys[]' | sort | uniq -c | sort -rnThe annotations are what make migration interesting. Common Ingress-NGINX annotations and their Gateway API equivalents:
| Ingress-NGINX Annotation | Gateway API Equivalent |
|---|---|
nginx.ingress.kubernetes.io/rewrite-target | HTTPRoute URLRewrite filter |
nginx.ingress.kubernetes.io/ssl-redirect | HTTPRoute RequestRedirect filter |
nginx.ingress.kubernetes.io/proxy-body-size | Implementation-specific policy |
nginx.ingress.kubernetes.io/rate-limiting | Implementation-specific policy |
nginx.ingress.kubernetes.io/cors-* | HTTPRoute ResponseHeaderModifier or policy |
nginx.ingress.kubernetes.io/auth-url | Implementation-specific (ExtAuth) |
nginx.ingress.kubernetes.io/canary-* | HTTPRoute backendRefs with weights |
nginx.ingress.kubernetes.io/affinity | Implementation-specific policy |
Some annotations map directly to Gateway API features. Others require implementation-specific policies (which means they depend on which Gateway API controller you choose).
Step 1: Choose a Gateway API Implementation
You need a controller that implements the Gateway API. Here are the major options in 2026:
Envoy Gateway
The official Envoy-based Gateway API implementation, backed by the Envoy Proxy community.
Pros: Strong community, excellent L7 features, extensible with Envoy filters
Cons: Heavier resource footprint than NGINX
Best for: Teams already using Envoy or Istio
Traefik
Traefik has supported Gateway API since v3.0 and is one of the most mature implementations.
Pros: Lightweight, great dashboard, easy to configure, middleware support
Cons: Some advanced features require Traefik-specific CRDs
Best for: Teams wanting a lightweight, batteries-included solution
NGINX Gateway Fabric
NGINX's own Gateway API implementation. If you're comfortable with NGINX, this is the closest migration path.
Pros: Familiar NGINX engine, straightforward migration from Ingress-NGINX
Cons: Smaller community than Envoy Gateway or Traefik
Best for: Teams that want to stay in the NGINX ecosystem
Cilium Gateway API
If you're running Cilium as your CNI, it has a built-in Gateway API implementation powered by eBPF.
Pros: No additional proxy pods, eBPF-powered performance, integrated with Cilium policies
Cons: Requires Cilium as CNI
Best for: Teams already running Cilium
For this guide, I'll use Envoy Gateway as the example, but the Gateway API resources (GatewayClass, Gateway, HTTPRoute) are the same regardless of implementation.
Step 2: Install Gateway API CRDs
The Gateway API CRDs are maintained separately from any implementation. Install them first:
# Install the standard Gateway API CRDs (v1.2.0 as of March 2026)
kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.2.0/standard-install.yaml
# Verify CRDs are installed
kubectl get crd | grep gatewayYou should see:
gatewayclasses.gateway.networking.k8s.io
gateways.gateway.networking.k8s.io
httproutes.gateway.networking.k8s.io
referencegrants.gateway.networking.k8s.io
Step 3: Install Your Gateway API Controller
For Envoy Gateway:
# Install Envoy Gateway using Helm
helm repo add envoy-gateway https://charts.envoyproxy.io
helm repo update
helm install envoy-gateway envoy-gateway/gateway-helm \
--namespace envoy-gateway-system \
--create-namespace \
--version v1.3.0For Traefik:
helm repo add traefik https://traefik.github.io/charts
helm repo update
helm install traefik traefik/traefik \
--namespace traefik-system \
--create-namespace \
--set providers.kubernetesGateway.enabled=trueVerify the GatewayClass is available:
kubectl get gatewayclassNAME CONTROLLER ACCEPTED AGE
envoy-gateway gateway.envoyproxy.io/gatewayclass True 30s
Step 4: Create a Gateway
The Gateway resource replaces the "listener" part of your Ingress controller. This is where you define which ports and protocols to accept, and which TLS certificates to use.
Before (Ingress-NGINX):
Your Ingress controller was configured via Helm values or a ConfigMap to listen on ports 80 and 443.
After (Gateway API):
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: main-gateway
namespace: gateway-system
spec:
gatewayClassName: envoy-gateway
listeners:
- name: http
protocol: HTTP
port: 80
allowedRoutes:
namespaces:
from: All
- name: https
protocol: HTTPS
port: 443
tls:
mode: Terminate
certificateRefs:
- kind: Secret
name: wildcard-tls
namespace: gateway-system
allowedRoutes:
namespaces:
from: AllKey differences:
allowedRoutes.namespaces.from: Alllets any namespace attach HTTPRoutes to this Gateway. You can restrict this to specific namespaces or use label selectors.- TLS is configured on the Gateway listener, not on individual routes.
- The Gateway gets its own LoadBalancer Service automatically.
Apply it:
kubectl apply -f gateway.yaml
# Check the Gateway status
kubectl get gateway main-gateway -n gateway-systemNAME CLASS ADDRESS PROGRAMMED AGE
main-gateway envoy-gateway 34.120.55.88 True 45s
Note the external IP — this is the new entry point for your traffic.
Step 5: Convert Ingress Resources to HTTPRoutes
This is the core of the migration. Let's convert real-world examples.
Example 1: Simple Host-Based Routing
Before (Ingress):
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: my-app-ingress
namespace: default
annotations:
nginx.ingress.kubernetes.io/ssl-redirect: "true"
spec:
ingressClassName: nginx
tls:
- hosts:
- myapp.example.com
secretName: myapp-tls
rules:
- host: myapp.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: my-app
port:
number: 80After (HTTPRoute):
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: my-app-route
namespace: default
spec:
parentRefs:
- name: main-gateway
namespace: gateway-system
hostnames:
- myapp.example.com
rules:
- matches:
- path:
type: PathPrefix
value: /
backendRefs:
- name: my-app
port: 80Notice how much cleaner this is. No annotations. No TLS config (it's on the Gateway). The HTTPRoute just says "for this hostname, route to this backend."
Example 2: Path-Based Routing with Rewrite
Before (Ingress):
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: api-ingress
namespace: default
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /$2
spec:
ingressClassName: nginx
rules:
- host: api.example.com
http:
paths:
- path: /v1(/|$)(.*)
pathType: ImplementationSpecific
backend:
service:
name: api-v1
port:
number: 8080
- path: /v2(/|$)(.*)
pathType: ImplementationSpecific
backend:
service:
name: api-v2
port:
number: 8080After (HTTPRoute):
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: api-route
namespace: default
spec:
parentRefs:
- name: main-gateway
namespace: gateway-system
hostnames:
- api.example.com
rules:
- matches:
- path:
type: PathPrefix
value: /v1
filters:
- type: URLRewrite
urlRewrite:
path:
type: ReplacePrefixMatch
replacePrefixMatch: /
backendRefs:
- name: api-v1
port: 8080
- matches:
- path:
type: PathPrefix
value: /v2
filters:
- type: URLRewrite
urlRewrite:
path:
type: ReplacePrefixMatch
replacePrefixMatch: /
backendRefs:
- name: api-v2
port: 8080The rewrite is now a first-class URLRewrite filter instead of a regex-heavy annotation. Much easier to read and debug.
Example 3: Canary Deployment (Traffic Splitting)
Before (Ingress-NGINX canary annotations):
# Primary Ingress
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: my-app-primary
annotations:
nginx.ingress.kubernetes.io/ssl-redirect: "true"
spec:
ingressClassName: nginx
rules:
- host: myapp.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: my-app-stable
port:
number: 80
---
# Canary Ingress
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: my-app-canary
annotations:
nginx.ingress.kubernetes.io/canary: "true"
nginx.ingress.kubernetes.io/canary-weight: "10"
spec:
ingressClassName: nginx
rules:
- host: myapp.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: my-app-canary
port:
number: 80After (HTTPRoute with traffic splitting):
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: my-app-route
namespace: default
spec:
parentRefs:
- name: main-gateway
namespace: gateway-system
hostnames:
- myapp.example.com
rules:
- matches:
- path:
type: PathPrefix
value: /
backendRefs:
- name: my-app-stable
port: 80
weight: 90
- name: my-app-canary
port: 80
weight: 10One resource instead of two. Weights are explicit. No annotation magic.
Example 4: HTTP to HTTPS Redirect
Before (Ingress):
annotations:
nginx.ingress.kubernetes.io/ssl-redirect: "true"
nginx.ingress.kubernetes.io/force-ssl-redirect: "true"After (HTTPRoute):
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: http-redirect
namespace: default
spec:
parentRefs:
- name: main-gateway
namespace: gateway-system
sectionName: http # Attach to HTTP listener only
hostnames:
- myapp.example.com
rules:
- filters:
- type: RequestRedirect
requestRedirect:
scheme: https
statusCode: 301Step 6: Cross-Namespace Routing with ReferenceGrant
One of Gateway API's best features is cross-namespace routing. If your HTTPRoute in namespace app-team needs to reference a backend in namespace shared-services, you need a ReferenceGrant:
apiVersion: gateway.networking.k8s.io/v1beta1
kind: ReferenceGrant
metadata:
name: allow-app-team-routes
namespace: shared-services
spec:
from:
- group: gateway.networking.k8s.io
kind: HTTPRoute
namespace: app-team
to:
- group: ""
kind: ServiceThis says: "HTTPRoutes in the app-team namespace are allowed to reference Services in the shared-services namespace." This is a security improvement over Ingress, where any namespace could route to any service.
Step 7: Test Side-by-Side
Don't cut over immediately. Run both Ingress-NGINX and the Gateway API controller simultaneously.
- Deploy the Gateway with a separate LoadBalancer IP
- Create HTTPRoutes that mirror your existing Ingress resources
- Test with curl using the Gateway's IP directly:
# Test against the new Gateway
curl -H "Host: myapp.example.com" https://34.120.55.88/
# Compare with the old Ingress controller
curl -H "Host: myapp.example.com" https://35.200.10.20/- Run automated tests against the new Gateway
- Monitor error rates and latency on both paths
Step 8: DNS Cutover
Once you've validated the Gateway API routes:
- Update your DNS records to point to the new Gateway's LoadBalancer IP
- Keep the old Ingress-NGINX controller running for 24-48 hours (DNS TTL + cache clearing)
- Monitor for any traffic still hitting the old controller
- Once traffic drops to zero on the old controller, decommission it:
# Remove old Ingress resources
kubectl delete ingress --all -A
# Uninstall Ingress-NGINX
helm uninstall ingress-nginx -n ingress-nginx
kubectl delete namespace ingress-nginxCommon Migration Pitfalls
1. Forgetting to Create ReferenceGrants
If your HTTPRoute references a Gateway in another namespace, you need a ReferenceGrant. Without it, the route will show Accepted: False in its status.
2. TLS Certificate Namespace
The Gateway references TLS secrets. If the secret is in a different namespace than the Gateway, you need a ReferenceGrant for that too.
3. Default Backend
Ingress has a defaultBackend concept. Gateway API handles this with a catch-all HTTPRoute:
rules:
- backendRefs:
- name: default-backend
port: 80No matches means "match everything."
4. Rate Limiting and Auth
These features are implementation-specific in Gateway API. Check your chosen controller's documentation for their policy CRDs.
If you're new to Kubernetes networking concepts, KodeKloud has excellent hands-on labs that cover Ingress, Services, and now Gateway API. Building muscle memory with real clusters is the fastest way to learn. For a test cluster to practice the migration, DigitalOcean Kubernetes clusters are affordable and quick to spin up.
Final Thoughts
Migrating from Ingress-NGINX to Gateway API isn't just about following the ecosystem — it's about getting access to a genuinely better networking model. The separation of concerns (GatewayClass → Gateway → HTTPRoute), first-class traffic splitting, cross-namespace routing, and the end of annotation-driven configuration are real improvements.
The migration is straightforward: audit your Ingress resources, install a Gateway API controller, convert your Ingress YAML to HTTPRoutes, test side-by-side, and cut over DNS. Most teams complete the migration in 1-2 weeks.
Start now. Your future self will thank you when you need canary deployments, header-based routing, or multi-team gateway management — and it just works.
Stay ahead of the curve
Get the latest DevOps, Kubernetes, AWS, and AI/ML guides delivered straight to your inbox. No spam — just practical engineering content.
Related Articles
How to Set Up Kubernetes Gateway API to Replace Ingress (2026 Guide)
The Kubernetes Ingress API is being replaced by the Gateway API. Here's a complete step-by-step guide to setting it up with Nginx Gateway Fabric and migrating from Ingress.
What is a Service Mesh? Explained Simply (No Jargon)
Service mesh sounds complicated but the concept is simple. Here's what it actually does, why teams use it, and whether you need one — explained without the buzzwords.
Agentic Networking — How Kubernetes Is Adapting for AI Agent Traffic in 2026
AI agents are the next-gen microservices, but with unpredictable communication patterns. Learn how Kubernetes networking, Gateway API, Cilium, and eBPF are adapting for agentic traffic in 2026.