The retirement of ingress-nginx was announced in November 2025, with the final shutdown occurring on March 24, 2026. If you are still running ingress-nginx, you need a migration path. This guide walks through moving to the Kubernetes Gateway API using Envoy Gateway, providing a production-ready approach that minimizes downtime and preserves your existing traffic routing configurations.
Goal
Migrate existing Ingress resources to Gateway API HTTPRoutes without downtime, preserving traffic routing, TLS termination, and path-based rules. This migration ensures your services remain accessible while transitioning from the deprecated ingress-nginx controller to the modern, extensible Gateway API architecture.
Prerequisites
- Kubernetes cluster v1.25+ with working ingress-nginx controller
- kubectl and Helm installed locally
- Envoy Gateway CRDs:
gateway.networking.k8s.io/v1 - Cluster admin permissions for Gateway resource creation
- Backup of existing Ingress manifests
Step 1: Install Envoy Gateway
Deploy Envoy Gateway alongside your existing ingress-nginx. Running both during migration prevents downtime and allows gradual traffic cutover. This parallel deployment approach ensures you can validate functionality before removing the old controller.
helm repo add envoy-gateway https://charts.envoyproxy.io/
helm repo update
helm install eg envoy-gateway/gateway-helm -n envoy-gateway-system --create-namespace
# Verify installation
kubectl get pods -n envoy-gateway-system
Step 2: Create a Gateway Resource
Define where Envoy listens. Unlike ingress-nginx’s global ConfigMap, Gateway API uses explicit Gateway objects that declare listeners, TLS settings, and route attachment policies. This explicit configuration model provides better security and separation of concerns.
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: production-gw
namespace: envoy-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:
- name: production-tls
allowedRoutes:
namespaces:
from: All
Step 3: Convert Ingress to HTTPRoute
Map your existing Ingress rules to HTTPRoutes. Understanding the key differences between the two APIs is crucial for a successful migration:
| Ingress-NGINX | Gateway API |
|---|---|
ingressClassName | parentRefs to Gateway |
nginx.ingress.kubernetes.io/* annotations | Gateway API route filters |
| Implicit pathType | Explicit path.type |
| Single host per rule | Multiple hostnames supported |
Example migration showing how annotations translate to filters:
# Old Ingress with rewrite annotation
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: api-ingress
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
spec:
rules:
- host: api.example.com
http:
paths:
- path: /v1
pathType: Prefix
backend:
service:
name: api-service
port:
number: 80
# New HTTPRoute with URLRewrite filter
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: api-route
spec:
parentRefs:
- name: production-gw
namespace: envoy-gateway-system
hostnames:
- api.example.com
rules:
- matches:
- path:
type: PathPrefix
value: /v1
filters:
- type: URLRewrite
urlRewrite:
path:
type: ReplacePrefixMatch
replacePrefixMatch: /
backendRefs:
- name: api-service
port: 80
Step 4: Validate Routing
Test traffic flows through Envoy Gateway before cutting over production traffic. Validation ensures your HTTPRoutes are correctly configured and backends are healthy.
# Port-forward to Envoy Gateway
kubectl port-forward -n envoy-gateway-system svc/envoy-gateway-production-gw 8080:80
# Test with curl using Host header
curl -H "Host: api.example.com" http://localhost:8080/v1/health
# Verify response from your backend
curl -I -H "Host: api.example.com" http://localhost:8080/v1/users
Step 5: Cutover DNS
Once validation passes, point your DNS or load balancer to Envoy Gateway’s external IP. This is the final step that switches production traffic to the new controller.
# Get Envoy Gateway external IP
kubectl get svc -n envoy-gateway-system envoy-gateway-production-gw
# Update your DNS records or load balancer target
# Example with external-dns annotation:
# kubectl annotate service envoy-gateway-production-gw \
# external-dns.alpha.kubernetes.io/hostname=api.example.com
Common Pitfalls
- Implicit defaults disappearing: proxy timeouts, upstream keepalive, and buffer sizes set in ingress-nginx ConfigMaps do not transfer. Document these before migration.
- Annotation behavior: Gateway API has no direct equivalent for many nginx annotations. Use HTTPRoute filters for rewrites, redirects, and header manipulation.
- Certificate management: If using cert-manager, update Certificate resources to reference Gateway instead of Ingress. The TLS secret reference format differs.
- Path matching semantics: Gateway API PathPrefix is not identical to Ingress Prefix. Test edge cases like trailing slashes and overlapping paths.
Verify
# Check HTTPRoute status shows accepted
kubectl get httproute api-route -o json | jq '.status.parents'
# Verify backends are healthy in Envoy logs
kubectl logs -n envoy-gateway-system deployment/envoy-gateway | grep "cluster_membership_changed"
# Monitor for 404s or 503s after cutover
kubectl logs -n envoy-gateway-system deployment/envoy-gateway | grep "HTTP" | tail -20
