How to Migrate from Ingress-NGINX to Kubernetes Gateway API

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-NGINXGateway API
ingressClassNameparentRefs to Gateway
nginx.ingress.kubernetes.io/* annotationsGateway API route filters
Implicit pathTypeExplicit path.type
Single host per ruleMultiple 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

Sources