# How to Migrate from Ingress-NGINX to Kubernetes Gateway API

- Link: https://thestackobserver.com/how-to-migrate-from-ingress-nginx-to-kubernetes-gateway-api/
- Published: 2026-04-17T15:59:15-04:00
- Author: Stackxx

The [retirement of ingress-nginx](https://kubernetes.io/blog/2025/11/11/ingress-nginx-retirement/)
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.

    ```wp-block-code
    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.

    ```wp-block-code
    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:

    ```wp-block-code
    # 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
    ```

    ```wp-block-code
    # 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.

    ```wp-block-code
    # 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.

    ```wp-block-code
    # 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

    ```wp-block-code
    # 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

 * [Kubernetes: Ingress-NGINX Retirement Announcement](https://kubernetes.io/blog/2025/11/11/ingress-nginx-retirement/)
 * [Kubernetes Gateway API Documentation](https://gateway-api.sigs.k8s.io/)
 * [CNCF: Ingress-NGINX to Envoy Gateway Migration](https://www.cncf.io/blog/2026/04/13/ingress-nginx-to-envoy-gateway-migration-on-cncf-internal-services-cluster/)
