Join our Discord Server
Collabnix Team The Collabnix Team is a diverse collective of Docker, Kubernetes, and IoT experts united by a passion for cloud-native technologies. With backgrounds spanning across DevOps, platform engineering, cloud architecture, and container orchestration, our contributors bring together decades of combined experience from various industries and technical domains.

Nginx Ingress Migration: Your Complete 2025 Guide

16 min read

Table of Contents

Nginx Ingress Migration: Your Complete 2025 Guide


Introduction: Why You Need to Migrate Now

If you’re running Kubernetes workloads with NGINX Ingress Controller, you need to act fast. The official NGINX Ingress Controller retirement is scheduled for March 2026, marking the end of security updates, bug fixes, and community support. With approximately four months remaining, organizations worldwide are rushing to implement migration strategies.

This comprehensive guide will walk you through migrating from NGINX Ingress to the Kubernetes Gateway API using the ingress2gateway tool, providing you with practical examples, common pitfalls, and production-ready strategies.


Understanding the NGINX Ingress Sunset {#understanding-the-sunset}

Why is NGINX Ingress Being Retired?

The NGINX Ingress Controller was originally created as a reference implementation to demonstrate the Ingress API concept. Nobody anticipated it would become the de facto standard for Kubernetes traffic routing. However, several factors led to its retirement:

  • Maintainer Burnout: The project relied heavily on 1-2 maintainers working in their spare time
  • Security Concerns: The flexibility to inject arbitrary NGINX configuration through annotations became a security liability
  • Scalability Issues: Community contribution didn’t scale with massive adoption
  • Maintenance Nightmare: What was once a feature became increasingly difficult to maintain

Timeline and Impact

  • Current Status: Updates are released on a best-effort basis, limited to critical security fixes
  • March 2026: Complete end of support, no security patches, no bug fixes
  • Impact: Approximately 40-50% of Kubernetes clusters use NGINX Ingress Controller

This affects millions of production workloads worldwide.


What is Kubernetes Gateway API? {#what-is-gateway-api}

The Kubernetes Gateway API is the next-generation standard for service networking in Kubernetes, officially supported by SIG Network. It’s not just an Ingress replacement – it’s a complete reimagining of how traffic routing should work.

Key Components

Gateway API introduces three primary resources:

# GatewayClass - Infrastructure template (managed by cluster admins)
apiVersion: gateway.networking.k8s.io/v1
kind: GatewayClass
metadata:
  name: nginx-gateway-class
spec:
  controllerName: nginx-gateway.io/controller

---
# Gateway - Actual load balancer instance
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: production-gateway
  namespace: default
spec:
  gatewayClassName: nginx-gateway-class
  listeners:
  - name: http
    protocol: HTTP
    port: 80
  - name: https
    protocol: HTTPS
    port: 443
    tls:
      mode: Terminate
      certificateRefs:
      - name: example-tls-secret

---
# HTTPRoute - Traffic routing rules (managed by developers)
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: example-route
  namespace: default
spec:
  parentRefs:
  - name: production-gateway
  hostnames:
  - "example.com"
  rules:
  - matches:
    - path:
        type: PathPrefix
        value: /api
    backendRefs:
    - name: api-service
      port: 8080

Why Gateway API Over Traditional Ingress? {#why-gateway-api}

Comparison: Ingress vs Gateway API

FeatureIngressGateway API
Role SeparationSingle resource for everythingSeparate resources for infra/dev concerns
Protocol SupportHTTP/HTTPS onlyHTTP, HTTPS, TCP, UDP, gRPC
Routing CapabilitiesBasic path/host routingAdvanced: traffic splitting, header matching, mirroring
ExtensibilityVendor-specific annotationsStandardized extension points
Cross-Namespace RoutingNot supportedNative support with explicit grants
Traffic WeightingVia annotations (inconsistent)Native built-in feature
Conformance TestingNo standardComprehensive conformance reports

Key Advantages of Gateway API

  1. Role-Oriented Design
    • Infrastructure providers manage GatewayClass
    • Cluster operators manage Gateway
    • Application developers manage Routes
    • Clear separation of concerns and responsibilities
  2. Enhanced Security
    • Developers can’t accidentally inject gateway-level configuration
    • Explicit cross-namespace grants prevent unauthorized access
    • No arbitrary annotation-based configuration injection
  3. Portability
    • Switching between implementations (NGINX → Envoy → Traefik) is simpler
    • Common core API across all providers
    • Reduced vendor lock-in
  4. Advanced Features Built-In
    • Traffic splitting for canary deployments
    • Request/response header modification
    • Request mirroring for testing
    • URL rewriting and redirection
    • Timeout and retry policies
  5. Future-Proof
    • Active development by SIG Network
    • Regular updates and new features
    • Growing ecosystem support

Prerequisites for Migration {#prerequisites}

Before starting your migration, ensure you have:

Required Components

  1. Kubernetes Cluster (v1.23+) kubectl version --short
  2. Gateway API CRDs installed kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.0.0/standard-install.yaml
  3. Gateway Controller (choose one):
    • Envoy Gateway
    • NGINX Gateway Fabric
    • Traefik
    • Cilium Gateway API
    • Istio Gateway
  4. Access to existing Ingress resources kubectl get ingress --all-namespaces

Recommended Tools

  • kubectl (latest version)
  • Go (1.20+) for installing ingress2gateway
  • jq for JSON processing
  • yq for YAML manipulation

Environment Check

# Check current Ingress resources
kubectl get ingress --all-namespaces -o wide

# Check Ingress controller version
kubectl get pods -n ingress-nginx -o yaml | grep image:

# Verify Gateway API CRDs
kubectl get crd | grep gateway

# Expected output:
# gatewayclasses.gateway.networking.k8s.io
# gateways.gateway.networking.k8s.io
# httproutes.gateway.networking.k8s.io
# referencegrants.gateway.networking.k8s.io

Installing ingress2gateway Tool {#installing-ingress2gateway}

The ingress2gateway tool is an official Kubernetes SIG Network project that automates conversion from Ingress to Gateway API resources.

Installation Methods

Method 1: Using Go (Recommended)

# Install latest version
go install github.com/kubernetes-sigs/ingress2gateway@latest

# Verify installation
ingress2gateway version

# The binary will be at:
# $(go env GOPATH)/bin/ingress2gateway

Method 2: Download Binary

# For Linux AMD64
wget https://github.com/kubernetes-sigs/ingress2gateway/releases/download/v0.4.0/ingress2gateway_linux_amd64.tar.gz
tar -xzf ingress2gateway_linux_amd64.tar.gz
sudo mv ingress2gateway /usr/local/bin/
chmod +x /usr/local/bin/ingress2gateway

# For macOS
wget https://github.com/kubernetes-sigs/ingress2gateway/releases/download/v0.4.0/ingress2gateway_darwin_amd64.tar.gz
tar -xzf ingress2gateway_darwin_amd64.tar.gz
sudo mv ingress2gateway /usr/local/bin/

Method 3: Homebrew (macOS)

# Add tap
brew tap kubernetes-sigs/ingress2gateway

# Install
brew install ingress2gateway

Verify Installation

# Check version
ingress2gateway version

# View help
ingress2gateway --help

# List supported providers
ingress2gateway print --help | grep providers

Step-by-Step Migration Process {#migration-process}

Phase 1: Assessment and Planning

Step 1: Inventory Your Ingress Resources

# List all Ingress resources
kubectl get ingress --all-namespaces -o yaml > current-ingress-backup.yaml

# Count Ingress resources per namespace
kubectl get ingress --all-namespaces --no-headers | awk '{print $1}' | sort | uniq -c

# Identify Ingress classes
kubectl get ingress --all-namespaces -o jsonpath='{range .items[*]}{.metadata.namespace}{"\t"}{.spec.ingressClassName}{"\n"}{end}' | sort -u

# Check annotations (these may need special handling)
kubectl get ingress --all-namespaces -o json | jq -r '.items[].metadata.annotations | keys[]' | sort -u

Step 2: Analyze Complex Configurations

# Find Ingress resources with TLS
kubectl get ingress --all-namespaces -o json | jq '.items[] | select(.spec.tls != null) | {namespace: .metadata.namespace, name: .metadata.name}'

# Find Ingress with multiple rules
kubectl get ingress --all-namespaces -o json | jq '.items[] | select((.spec.rules | length) > 1) | {namespace: .metadata.namespace, name: .metadata.name}'

# Identify custom annotations
kubectl get ingress --all-namespaces -o json | jq -r '.items[].metadata.annotations | to_entries[] | select(.key | startswith("nginx.ingress.kubernetes.io")) | .key' | sort -u

Phase 2: Test Conversion

Step 3: Convert Single Ingress (Dry Run)

# Convert specific Ingress to stdout
ingress2gateway print \
  --namespace=production \
  --ingress-name=my-app-ingress \
  --providers=ingress-nginx

# Save to file for review
ingress2gateway print \
  --namespace=production \
  --providers=ingress-nginx \
  > converted-gateway-resources.yaml

Step 4: Convert All Ingress in Namespace

# Convert all Ingress resources in a namespace
ingress2gateway print \
  --namespace=production \
  --providers=ingress-nginx \
  --output=yaml > production-gateways.yaml

# Review the output
cat production-gateways.yaml

# Check for any warnings or errors in conversion
grep -i "unsupported\|warning\|error" production-gateways.yaml

Step 5: Validate Generated Resources

# Validate YAML syntax
kubectl apply --dry-run=client -f production-gateways.yaml

# Check for resource conflicts
kubectl apply --dry-run=server -f production-gateways.yaml

# Validate Gateway API resources
kubectl apply --dry-run=server -f production-gateways.yaml 2>&1 | grep -i "error\|invalid"

Phase 3: Staging Deployment

Step 6: Create Test Environment

# Create migration testing namespace
kubectl create namespace gateway-migration-test

# Copy secrets and configmaps
kubectl get secrets -n production -o yaml | \
  sed 's/namespace: production/namespace: gateway-migration-test/' | \
  kubectl apply -f -

# Deploy sample application
kubectl apply -n gateway-migration-test -f - <<EOF
apiVersion: apps/v1
kind: Deployment
metadata:
  name: test-app
spec:
  replicas: 2
  selector:
    matchLabels:
      app: test-app
  template:
    metadata:
      labels:
        app: test-app
    spec:
      containers:
      - name: nginx
        image: nginx:latest
        ports:
        - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: test-app-service
spec:
  selector:
    app: test-app
  ports:
  - port: 80
    targetPort: 80
EOF

Step 7: Deploy Gateway Resources

# Apply converted Gateway API resources to test namespace
ingress2gateway print \
  --namespace=gateway-migration-test \
  --providers=ingress-nginx | \
  kubectl apply -f -

# Verify Gateway creation
kubectl get gateways -n gateway-migration-test
kubectl get httproutes -n gateway-migration-test

# Check Gateway status
kubectl describe gateway production-gateway -n gateway-migration-test

Step 8: Test Connectivity

# Get Gateway external IP
GATEWAY_IP=$(kubectl get svc -n gateway-migration-test \
  -l gateway.networking.k8s.io/gateway-name=production-gateway \
  -o jsonpath='{.items[0].status.loadBalancer.ingress[0].ip}')

echo "Gateway IP: $GATEWAY_IP"

# Test HTTP endpoint
curl -H "Host: test-app.example.com" http://$GATEWAY_IP/

# Test with verbose output
curl -v -H "Host: test-app.example.com" http://$GATEWAY_IP/

# Load testing (optional)
ab -n 1000 -c 10 -H "Host: test-app.example.com" http://$GATEWAY_IP/

Phase 4: Production Migration

Step 9: Parallel Deployment Strategy

The safest approach is running both Ingress and Gateway API simultaneously:

# Keep existing Ingress running
kubectl get ingress -n production

# Deploy Gateway API resources alongside
ingress2gateway print \
  --namespace=production \
  --providers=ingress-nginx | \
  kubectl apply -f -

# Both systems are now running in parallel
# Ingress: Old traffic
# Gateway: New traffic (if DNS/LB updated)

Step 10: Gradual Traffic Migration

# Option A: DNS-based migration
# Update DNS A record gradually:
# - 10% traffic to Gateway IP
# - Monitor for 24 hours
# - Increase to 50%
# - Monitor for 24 hours
# - 100% to Gateway

# Option B: LoadBalancer IP preservation
# If you need to keep the same IP:

# Get current Ingress LoadBalancer IP
CURRENT_IP=$(kubectl get svc -n ingress-nginx ingress-nginx-controller \
  -o jsonpath='{.status.loadBalancer.ingress[0].ip}')

# Update Gateway service to use same IP
kubectl patch svc -n production gateway-production-gateway \
  -p "{\"spec\":{\"loadBalancerIP\":\"$CURRENT_IP\"}}"

# Delete old Ingress LoadBalancer
kubectl delete svc -n ingress-nginx ingress-nginx-controller

Step 11: Monitor and Validate

# Monitor Gateway logs
kubectl logs -n production -l gateway.networking.k8s.io/gateway-name=production-gateway -f

# Check HTTPRoute status
kubectl get httproutes -n production -o wide

# Verify backend connectivity
kubectl get httproutes -n production -o json | \
  jq '.items[] | {name: .metadata.name, status: .status.parents[0].conditions}'

# Monitor application logs
kubectl logs -n production -l app=your-app --tail=100 -f

# Check for errors
kubectl get events -n production --sort-by='.lastTimestamp' | grep -i error

Phase 5: Cleanup

Step 12: Remove Old Ingress Resources

⚠️ Warning: Only do this after confirming Gateway is working correctly!

# Backup before deletion
kubectl get ingress --all-namespaces -o yaml > ingress-final-backup.yaml

# Delete specific Ingress
kubectl delete ingress my-app-ingress -n production

# Or delete all Ingress in namespace (use with caution)
kubectl delete ingress --all -n production

# Uninstall NGINX Ingress Controller
helm uninstall ingress-nginx -n ingress-nginx

# Remove namespace
kubectl delete namespace ingress-nginx

Real-World Migration Examples {#real-world-examples}

Example 1: Simple HTTP Ingress Migration

Original Ingress:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: simple-app-ingress
  namespace: production
  annotations:
    kubernetes.io/ingress.class: nginx
spec:
  rules:
  - host: app.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: app-service
            port:
              number: 80

Convert with ingress2gateway:

ingress2gateway print \
  --namespace=production \
  --ingress-name=simple-app-ingress \
  --providers=ingress-nginx

Resulting Gateway API Resources:

apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: simple-app-ingress-gateway
  namespace: production
spec:
  gatewayClassName: nginx
  listeners:
  - name: http
    protocol: HTTP
    port: 80
    allowedRoutes:
      namespaces:
        from: Same

---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: simple-app-ingress-app-example-com
  namespace: production
spec:
  parentRefs:
  - name: simple-app-ingress-gateway
  hostnames:
  - "app.example.com"
  rules:
  - matches:
    - path:
        type: PathPrefix
        value: /
    backendRefs:
    - name: app-service
      port: 80

Example 2: HTTPS with TLS Termination

Original Ingress:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: secure-app-ingress
  namespace: production
spec:
  ingressClassName: nginx
  tls:
  - hosts:
    - secure.example.com
    secretName: tls-secret
  rules:
  - host: secure.example.com
    http:
      paths:
      - path: /api
        pathType: Prefix
        backend:
          service:
            name: api-service
            port:
              number: 8080
      - path: /web
        pathType: Prefix
        backend:
          service:
            name: web-service
            port:
              number: 3000

Converted Gateway API:

apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: secure-app-gateway
  namespace: production
spec:
  gatewayClassName: nginx
  listeners:
  - name: https
    protocol: HTTPS
    port: 443
    hostname: "secure.example.com"
    tls:
      mode: Terminate
      certificateRefs:
      - name: tls-secret
        kind: Secret
    allowedRoutes:
      namespaces:
        from: Same

---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: secure-app-route
  namespace: production
spec:
  parentRefs:
  - name: secure-app-gateway
    sectionName: https
  hostnames:
  - "secure.example.com"
  rules:
  - matches:
    - path:
        type: PathPrefix
        value: /api
    backendRefs:
    - name: api-service
      port: 8080
  - matches:
    - path:
        type: PathPrefix
        value: /web
    backendRefs:
    - name: web-service
      port: 3000

Example 3: Advanced Routing with Header Matching

Original Ingress (with annotations):

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: advanced-routing
  namespace: production
  annotations:
    nginx.ingress.kubernetes.io/canary: "true"
    nginx.ingress.kubernetes.io/canary-by-header: "X-Canary"
    nginx.ingress.kubernetes.io/canary-by-header-value: "true"
spec:
  ingressClassName: nginx
  rules:
  - host: api.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: api-v2-service
            port:
              number: 8080

Equivalent Gateway API (manual enhancement needed):

apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: canary-routing
  namespace: production
spec:
  parentRefs:
  - name: api-gateway
  hostnames:
  - "api.example.com"
  rules:
  - matches:
    - path:
        type: PathPrefix
        value: /
      headers:
      - name: X-Canary
        value: "true"
    backendRefs:
    - name: api-v2-service
      port: 8080
  - matches:
    - path:
        type: PathPrefix
        value: /
    backendRefs:
    - name: api-v1-service
      port: 8080

Example 4: Multi-Namespace Routing

Gateway in infra namespace:

apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: shared-gateway
  namespace: infrastructure
spec:
  gatewayClassName: nginx
  listeners:
  - name: http
    protocol: HTTP
    port: 80
    allowedRoutes:
      namespaces:
        from: All  # Allow routes from any namespace

---
# ReferenceGrant to allow cross-namespace access
apiVersion: gateway.networking.k8s.io/v1beta1
kind: ReferenceGrant
metadata:
  name: allow-apps-to-gateway
  namespace: infrastructure
spec:
  from:
  - group: gateway.networking.k8s.io
    kind: HTTPRoute
    namespace: app-team-a
  - group: gateway.networking.k8s.io
    kind: HTTPRoute
    namespace: app-team-b
  to:
  - group: gateway.networking.k8s.io
    kind: Gateway
    name: shared-gateway

HTTPRoute in application namespace:

apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: team-a-route
  namespace: app-team-a
spec:
  parentRefs:
  - name: shared-gateway
    namespace: infrastructure  # Cross-namespace reference
  hostnames:
  - "team-a.example.com"
  rules:
  - matches:
    - path:
        type: PathPrefix
        value: /
    backendRefs:
    - name: team-a-service
      port: 80

Common Migration Pitfalls and Solutions {#common-pitfalls}

Pitfall 1: IP Address Changes

Problem: New Gateway LoadBalancer gets a different IP than Ingress.

Solution:

# Option 1: Preserve existing IP
OLD_IP=$(kubectl get svc ingress-nginx-controller -n ingress-nginx \
  -o jsonpath='{.status.loadBalancer.ingress[0].ip}')

kubectl patch svc gateway-svc -n production \
  -p "{\"spec\":{\"loadBalancerIP\":\"$OLD_IP\"}}"

# Option 2: Update DNS records
# Update your DNS A records to point to new Gateway IP

# Option 3: Use MetalLB IP pool
kubectl annotate svc gateway-svc -n production \
  metallb.universe.tf/address-pool=production-pool

Pitfall 2: Namespace Scoping Issues

Problem: HTTPRoute not attaching to Gateway due to incorrect allowedRoutes.

Error Message:

HTTPRoute "my-route" not accepted by Gateway "my-gateway": 
cross-namespace route not allowed

Solution:

# Fix 1: Update Gateway allowedRoutes
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: my-gateway
  namespace: infrastructure
spec:
  gatewayClassName: nginx
  listeners:
  - name: http
    protocol: HTTP
    port: 80
    allowedRoutes:
      namespaces:
        from: All  # Or use Selector for specific namespaces

---
# Fix 2: Create ReferenceGrant
apiVersion: gateway.networking.k8s.io/v1beta1
kind: ReferenceGrant
metadata:
  name: cross-namespace-grant
  namespace: infrastructure
spec:
  from:
  - group: gateway.networking.k8s.io
    kind: HTTPRoute
    namespace: application
  to:
  - group: gateway.networking.k8s.io
    kind: Gateway

Pitfall 3: TLS Secret Not Found

Problem: Gateway can’t find TLS certificate secret.

Error Message:

Gateway "my-gateway" listener "https" condition "ResolvedRefs" 
is False: secret "tls-cert" not found

Solution:

# Fix 1: Copy secret to Gateway namespace
kubectl get secret tls-cert -n app-namespace -o yaml | \
  sed 's/namespace: app-namespace/namespace: gateway-namespace/' | \
  kubectl apply -f -

# Fix 2: Create ReferenceGrant for cross-namespace secret access
kubectl apply -f - <<EOF
apiVersion: gateway.networking.k8s.io/v1beta1
kind: ReferenceGrant
metadata:
  name: allow-gateway-to-secret
  namespace: app-namespace
spec:
  from:
  - group: gateway.networking.k8s.io
    kind: Gateway
    namespace: gateway-namespace
  to:
  - group: ""
    kind: Secret
    name: tls-cert
EOF

Pitfall 4: Unsupported Annotations

Problem: NGINX Ingress annotations not translating to Gateway API.

Common Unsupported Annotations:

  • nginx.ingress.kubernetes.io/rewrite-target
  • nginx.ingress.kubernetes.io/auth-url
  • nginx.ingress.kubernetes.io/rate-limit
  • nginx.ingress.kubernetes.io/cors-*

Solution:

# Use Gateway API-native features or implementation-specific policies

# Example: URL Rewrite (depends on implementation)
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: rewrite-example
spec:
  parentRefs:
  - name: my-gateway
  rules:
  - matches:
    - path:
        type: PathPrefix
        value: /old-path
    filters:
    - type: URLRewrite
      urlRewrite:
        path:
          type: ReplacePrefixMatch
          replacePrefixMatch: /new-path
    backendRefs:
    - name: my-service
      port: 80

# Example: Rate Limiting (implementation-specific)
# For Envoy Gateway:
apiVersion: gateway.envoyproxy.io/v1alpha1
kind: BackendTrafficPolicy
metadata:
  name: rate-limit-policy
spec:
  targetRef:
    group: gateway.networking.k8s.io
    kind: HTTPRoute
    name: my-route
  rateLimit:
    type: Global
    global:
      rules:
      - limit:
          requests: 100
          unit: Minute

Pitfall 5: Default Backend Conversion

Problem: Ingress defaultBackend not converting properly.

Original Ingress:

spec:
  defaultBackend:
    service:
      name: default-404-service
      port:
        number: 80

Solution:

# Create catch-all HTTPRoute
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: default-backend-route
spec:
  parentRefs:
  - name: my-gateway
  # No hostnames = matches all
  rules:
  - matches:
    - path:
        type: PathPrefix
        value: /
    backendRefs:
    - name: default-404-service
      port: 80

Pitfall 6: MetalLB Controller Restart Required

Problem: Using MetalLB and IP addresses not assigning correctly.

Solution:

# Restart MetalLB controller after Gateway creation
kubectl rollout restart deployment controller -n metallb-system

# Verify IP assignment
kubectl get svc -n production -o wide | grep gateway

# Check MetalLB logs
kubectl logs -n metallb-system -l app=metallb,component=controller

Pitfall 7: Gateway Not Ready

Problem: Gateway status shows “Not Ready” or “No addresses”.

Diagnosis:

# Check Gateway status
kubectl describe gateway my-gateway -n production

# Check GatewayClass
kubectl get gatewayclass

# Verify controller is running
kubectl get pods -n gateway-system

# Check controller logs
kubectl logs -n gateway-system deployment/gateway-controller

Common Fixes:

# Fix 1: Ensure GatewayClass exists and references correct controller
kubectl get gatewayclass -o yaml

# Fix 2: Check if controller has proper RBAC permissions
kubectl get clusterrole gateway-controller -o yaml

# Fix 3: Verify cloud provider load balancer support
# For on-prem, ensure MetalLB or similar is installed

Testing and Validation Strategies {#testing-strategies}

Pre-Migration Testing Checklist

#!/bin/bash
# migration-test.sh - Comprehensive migration testing script

NAMESPACE="production"
GATEWAY_NAME="production-gateway"

echo "=== Gateway Migration Testing ==="

# Test 1: Verify Gateway is ready
echo "Test 1: Gateway Status"
kubectl get gateway $GATEWAY_NAME -n $NAMESPACE -o jsonpath='{.status.conditions[?(@.type=="Ready")].status}'

# Test 2: Check HTTPRoutes attachment
echo "Test 2: HTTPRoute Attachment"
kubectl get httproutes -n $NAMESPACE -o json | \
  jq -r '.items[] | select(.spec.parentRefs[0].name=="'$GATEWAY_NAME'") | .metadata.name'

# Test 3: Verify backend services are healthy
echo "Test 3: Backend Services"
kubectl get httproutes -n $NAMESPACE -o json | \
  jq -r '.items[].spec.rules[].backendRefs[].name' | \
  while read svc; do
    kubectl get svc $svc -n $NAMESPACE &> /dev/null && echo "$svc: OK" || echo "$svc: MISSING"
  done

# Test 4: DNS resolution
echo "Test 4: DNS Resolution"
kubectl get httproutes -n $NAMESPACE -o json | \
  jq -r '.items[].spec.hostnames[]' | \
  while read host; do
    dig +short $host | grep -q "." && echo "$host: Resolves" || echo "$host: No DNS"
  done

# Test 5: HTTP connectivity
echo "Test 5: HTTP Connectivity"
GATEWAY_IP=$(kubectl get svc -n $NAMESPACE \
  -l gateway.networking.k8s.io/gateway-name=$GATEWAY_NAME \
  -o jsonpath='{.items[0].status.loadBalancer.ingress[0].ip}')

kubectl get httproutes -n $NAMESPACE -o json | \
  jq -r '.items[].spec.hostnames[]' | \
  while read host; do
    STATUS=$(curl -s -o /dev/null -w "%{http_code}" -H "Host: $host" http://$GATEWAY_IP/)
    echo "$host: HTTP $STATUS"
  done

echo "=== Testing Complete ==="

Load Testing

# Install Apache Bench
sudo apt-get install apache2-utils -y

# Basic load test
GATEWAY_IP=$(kubectl get svc -n production gateway-svc \
  -o jsonpath='{.status.loadBalancer.ingress[0].ip}')

ab -n 10000 -c 100 -H "Host: app.example.com" http://$GATEWAY_IP/

# Advanced load test with different endpoints
for path in / /api /health; do
  echo "Testing $path"
  ab -n 1000 -c 50 -H "Host: app.example.com" http://$GATEWAY_IP$path
done

Performance Comparison

#!/bin/bash
# compare-performance.sh

echo "Testing Ingress Performance..."
ab -n 10000 -c 100 http://ingress-endpoint/ > ingress-results.txt

echo "Testing Gateway Performance..."
ab -n 10000 -c 100 http://gateway-endpoint/ > gateway-results.txt

echo "=== Comparison ==="
echo "Ingress:"
grep "Requests per second" ingress-results.txt
echo "Gateway:"
grep "Requests per second" gateway-results.txt

Monitoring During Migration

# prometheus-servicemonitor.yaml
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
  name: gateway-metrics
  namespace: monitoring
spec:
  selector:
    matchLabels:
      gateway.networking.k8s.io/gateway-name: production-gateway
  endpoints:
  - port: metrics
    interval: 30s

---
# grafana-dashboard-configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: gateway-dashboard
  namespace: monitoring
data:
  dashboard.json: |
    {
      "dashboard": {
        "title": "Gateway Migration Dashboard",
        "panels": [
          {
            "title": "Request Rate",
            "targets": [
              {
                "expr": "rate(gateway_api_http_requests_total[5m])"
              }
            ]
          },
          {
            "title": "Response Times",
            "targets": [
              {
                "expr": "histogram_quantile(0.95, rate(gateway_api_http_request_duration_seconds_bucket[5m]))"
              }
            ]
          },
          {
            "title": "Error Rate",
            "targets": [
              {
                "expr": "rate(gateway_api_http_requests_total{code=~\"5..\"}[5m])"
              }
            ]
          }
        ]
      }
    }

Alternative Ingress Controllers {#alternatives}

If you’re not ready for Gateway API, consider these NGINX Ingress alternatives:

Option 1: F5 NGINX Ingress Controller

Pros:

  • Similar annotations to community NGINX Ingress
  • Defined migration path from community version
  • Commercial support available with NGINX Plus

Cons:

  • Doesn’t support Gateway API (need NGINX Gateway Fabric separately)
  • Commercial features require licensing

Installation:

# Add F5 NGINX Helm repo
helm repo add nginx-stable https://helm.nginx.com/stable
helm repo update

# Install
helm install nginx-ingress nginx-stable/nginx-ingress \
  --namespace nginx-ingress \
  --create-namespace

Option 2: Traefik Proxy

Pros:

  • Supports both Ingress and Gateway API
  • NGINX Ingress annotation compatibility layer
  • Active development and community

Cons:

  • Different configuration paradigm
  • Some features require Traefik-specific CRDs

Installation:

# Add Traefik Helm repo
helm repo add traefik https://helm.traefik.io/traefik
helm repo update

# Install with NGINX annotation support
helm install traefik traefik/traefik \
  --namespace traefik \
  --create-namespace \
  --set providers.kubernetesIngress.enabled=true \
  --set providers.kubernetesGateway.enabled=true

Option 3: Envoy Gateway

Pros:

  • Built natively for Gateway API
  • High performance (CNCF project)
  • Modern architecture
  • Excellent for cloud-native applications

Cons:

  • No native Ingress support
  • Requires full Gateway API migration
  • Newer project (less mature than others)

Installation:

# Install Envoy Gateway
helm install envoy-gateway oci://docker.io/envoyproxy/gateway-helm \
  --namespace envoy-gateway-system \
  --create-namespace

Option 4: Cilium Gateway API

Pros:

  • Integrated with Cilium CNI
  • eBPF-based (excellent performance)
  • Native Gateway API support
  • Advanced network policies

Cons:

  • Requires Cilium as CNI
  • Complex if not already using Cilium

Installation:

# If Cilium is already installed
cilium install --set gatewayAPI.enabled=true

# Create GatewayClass
kubectl apply -f - <<EOF
apiVersion: gateway.networking.k8s.io/v1
kind: GatewayClass
metadata:
  name: cilium
spec:
  controllerName: io.cilium/gateway-controller
EOF

Comparison Matrix

ControllerGateway APIIngressPerformanceMaturityLicense
NGINX Gateway Fabric✅ Yes❌ NoHighMediumApache 2.0
F5 NGINX Ingress❌ No✅ YesHighHighApache 2.0
Traefik✅ Yes✅ YesMedium-HighHighMIT
Envoy Gateway✅ Yes❌ NoVery HighMediumApache 2.0
Cilium✅ Yes✅ YesVery HighHighApache 2.0
Istio✅ Yes✅ YesHighVery HighApache 2.0
Kong✅ Yes✅ YesHighHighApache 2.0

Migration Best Practices {#best-practices}

1. Start Early, Migrate Gradually

# Create migration phases
PHASES=(
  "development"
  "staging"
  "production-canary"
  "production-full"
)

for phase in "${PHASES[@]}"; do
  echo "Migrating $phase environment..."
  # Your migration commands here
  # Wait and validate before proceeding
done

2. Document Everything

# migration-log.md

## Migration Timeline
- 2025-01-10: Assessment complete, 47 Ingress resources identified
- 2025-01-15: Test conversion successful in dev environment
- 2025-01-20: Staging migration complete, monitoring for 1 week
- 2025-01-27: Production migration Phase 1 (20% traffic)

## Known Issues
1. Custom annotation `nginx.ingress.kubernetes.io/custom-header` 
   - Not supported in Gateway API
   - Manual HTTPRoute filter required
   - Issue #123 opened with vendor

## Rollback Procedures
1. Restore Ingress from backup: `kubectl apply -f ingress-backup.yaml`
2. Update DNS to old IPs
3. Delete Gateway resources

3. Automate with GitOps

# argocd-application.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: gateway-migration
  namespace: argocd
spec:
  project: default
  source:
    repoURL: https://github.com/your-org/gateway-configs
    targetRevision: main
    path: gateway-api
  destination:
    server: https://kubernetes.default.svc
    namespace: production
  syncPolicy:
    automated:
      prune: false  # Don't auto-delete during migration
      selfHeal: false
    syncOptions:
    - CreateNamespace=true

4. Test Rollback Procedures

#!/bin/bash
# test-rollback.sh

# Backup current state
kubectl get gateways,httproutes -n production -o yaml > gateway-backup.yaml

# Simulate rollback
kubectl delete gateways,httproutes -n production
kubectl apply -f ingress-backup.yaml

# Verify rollback
curl -v http://your-app.example.com

# Re-apply Gateway if test successful
kubectl apply -f gateway-backup.yaml

5. Implement Circuit Breakers

# health-check-policy.yaml (implementation-specific)
apiVersion: gateway.envoyproxy.io/v1alpha1
kind: BackendTrafficPolicy
metadata:
  name: health-check-policy
spec:
  targetRef:
    group: gateway.networking.k8s.io
    kind: HTTPRoute
    name: my-route
  healthCheck:
    active:
      type: HTTP
      http:
        path: /health
      timeout: 5s
      interval: 10s
      unhealthyThreshold: 3
      healthyThreshold: 1

6. Set Up Comprehensive Monitoring

# prometheus-alerts.yaml
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
  name: gateway-migration-alerts
spec:
  groups:
  - name: gateway-migration
    interval: 30s
    rules:
    - alert: GatewayHighErrorRate
      expr: |
        rate(gateway_api_http_requests_total{code=~"5.."}[5m]) > 0.05
      for: 5m
      annotations:
        summary: "Gateway error rate above 5%"
        description: "Check Gateway and HTTPRoute configurations"
    
    - alert: GatewayNotReady
      expr: |
        gateway_api_gateway_status{condition="Ready",status="False"} == 1
      for: 2m
      annotations:
        summary: "Gateway not in ready state"
    
    - alert: HTTPRouteNotAttached
      expr: |
        gateway_api_httproute_status{condition="Accepted",status="False"} == 1
      for: 5m
      annotations:
        summary: "HTTPRoute not attached to Gateway"

7. Maintain Feature Parity

# feature-parity-checklist.yaml
features:
  - name: "Basic HTTP routing"
    ingress: "✅ Supported"
    gateway: "✅ Supported"
    status: "Complete"
  
  - name: "TLS termination"
    ingress: "✅ Supported"
    gateway: "✅ Supported"
    status: "Complete"
  
  - name: "Custom headers"
    ingress: "✅ Via annotation"
    gateway: "✅ Via HTTPRoute filters"
    status: "Requires manual conversion"
  
  - name: "Rate limiting"
    ingress: "✅ Via annotation"
    gateway: "⚠️ Implementation-specific"
    status: "Vendor policy required"
  
  - name: "Authentication"
    ingress: "✅ oauth2-proxy annotation"
    gateway: "⚠️ External AuthN service"
    status: "Architecture change needed"

8. Create Reusable Templates

# gateway-template.yaml
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: ${APP_NAME}-gateway
  namespace: ${NAMESPACE}
  labels:
    app: ${APP_NAME}
    team: ${TEAM_NAME}
spec:
  gatewayClassName: ${GATEWAY_CLASS}
  listeners:
  - name: http
    protocol: HTTP
    port: 80
    allowedRoutes:
      namespaces:
        from: Same
  - name: https
    protocol: HTTPS
    port: 443
    hostname: "${APP_NAME}.${DOMAIN}"
    tls:
      mode: Terminate
      certificateRefs:
      - name: ${APP_NAME}-tls
        kind: Secret
    allowedRoutes:
      namespaces:
        from: Same

9. Educate Your Team

# Create training materials
cat > team-training.md <<EOF
# Gateway API Migration Training

## Key Differences
1. Gateway vs Ingress
2. HTTPRoute vs Ingress rules
3. ReferenceGrant for cross-namespace

## Common Tasks
### Creating a new route
\`\`\`bash
kubectl apply -f - <<YAML
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: my-app
spec:
  parentRefs:
  - name: shared-gateway
  hostnames:
  - "my-app.example.com"
  rules:
  - backendRefs:
    - name: my-service
      port: 80
YAML
\`\`\`

### Troubleshooting
1. Check Gateway status: \`kubectl describe gateway <name>\`
2. Check HTTPRoute status: \`kubectl describe httproute <name>\`
3. View logs: \`kubectl logs -l gateway.networking.k8s.io/gateway-name=<name>\`

## Resources
- Gateway API docs: https://gateway-api.sigs.k8s.io
- Internal wiki: https://wiki.company.com/gateway-api
- Slack channel: #gateway-migration
EOF

10. Plan for the Unexpected

# contingency-plan.yaml
scenarios:
  - scenario: "Gateway LoadBalancer fails to provision"
    detection: "Gateway status 'Ready' = False for >5 minutes"
    action: |
      1. Check cloud provider quotas
      2. Review service annotations
      3. Rollback to Ingress if critical
      4. Escalate to cloud provider support
  
  - scenario: "HTTPRoute not routing traffic"
    detection: "HTTPRoute status 'Accepted' = False"
    action: |
      1. Check parentRefs match Gateway name
      2. Verify namespace scoping
      3. Check ReferenceGrant if cross-namespace
      4. Review Gateway allowedRoutes
  
  - scenario: "Performance degradation"
    detection: "P95 latency >2x baseline"
    action: |
      1. Check Gateway controller resources
      2. Review Gateway configuration
      3. Scale Gateway pods if needed
      4. Consider reverting to Ingress temporarily
  
  - scenario: "TLS certificate issues"
    detection: "HTTPS endpoints returning SSL errors"
    action: |
      1. Verify secret exists in correct namespace
      2. Check ReferenceGrant for cross-namespace secrets
      3. Validate certificate expiry
      4. Review Gateway TLS configuration

Conclusion {#conclusion}

Migrating from NGINX Ingress to Kubernetes Gateway API is no longer optional – with the March 2026 retirement deadline fast approaching, organizations must act now. While the migration requires careful planning and execution, the benefits of Gateway API make it worthwhile:

Key Takeaways

  1. Start Planning Immediately – You have limited time before NGINX Ingress loses support
  2. Use ingress2gateway – Automate conversion where possible, but always review output
  3. Migrate Gradually – Don’t try to migrate everything at once
  4. Test Extensively – Use staging environments and parallel deployments
  5. Monitor Everything – Watch metrics closely during and after migration
  6. Document Your Journey – Help your team and the community learn from your experience

Next Steps

  1. Today: Inventory your Ingress resources and identify complex configurations
  2. This Week: Set up a test environment and experiment with ingress2gateway
  3. This Month: Migrate your development and staging environments
  4. Next Quarter: Begin gradual production migration

Additional Resources

  • Official Gateway API Documentation: https://gateway-api.sigs.k8s.io
  • ingress2gateway GitHub: https://github.com/kubernetes-sigs/ingress2gateway
  • Gateway API Conformance Reports: https://gateway-api.sigs.k8s.io/implementations/
  • Kubernetes SIG Network: https://github.com/kubernetes/community/tree/master/sig-network

Get Help

  • Gateway API Slack: #sig-network-gateway-api on Kubernetes Slack
  • GitHub Issues: Report problems or request features
  • Community Meetings: Join SIG Network meetings for discussions

The migration from NGINX Ingress to Gateway API represents a significant evolution in Kubernetes networking. While it requires effort upfront, the improved architecture, standardization, and future-proof design make it a worthwhile investment for any organization running production Kubernetes workloads.

Don’t wait until the last minute – start your migration journey today!

Have Queries? Join https://launchpass.com/collabnix

Collabnix Team The Collabnix Team is a diverse collective of Docker, Kubernetes, and IoT experts united by a passion for cloud-native technologies. With backgrounds spanning across DevOps, platform engineering, cloud architecture, and container orchestration, our contributors bring together decades of combined experience from various industries and technical domains.
Join our Discord Server
Table of Contents
Index