This content originally appeared on DEV Community and was authored by kelvin manavar
In Kubernetes, one of the most critical tasks is managing how services inside the cluster are exposed to the outside world. For a long time, the Ingress API has been the standard way to handle HTTP(S) traffic, acting as the gateway that routes requests from external clients to the right services within the cluster. While Ingress has served its purpose well, it comes with limitations—particularly around extensibility, consistency across implementations, and support for advanced traffic management scenarios.
To address these gaps, Kubernetes introduced the Gateway API, a next-generation alternative to Ingress. Unlike Ingress, which primarily focuses on simple routing, the Gateway API provides a richer, role-oriented model. It is designed to be more expressive, flexible, and vendor-neutral, making it easier to define complex networking behaviors while maintaining a consistent user experience across different environments.
If your organization is currently relying on Ingress and considering a migration to the Gateway API, this guide will walk you through the process. We’ll explore why the Gateway API is worth adopting, what changes you need to be aware of, and the practical steps to migrate from your existing Ingress setup to the modern Gateway API within a running Kubernetes cluster.
To explore the reasons for migration, follow this link:
Link
Challenges in Migrating to Gateway API
Although the Gateway API brings powerful improvements over Ingress, transitioning to it is not always straightforward. Organizations should be prepared for the following challenges:
- Controller Support – Not all Ingress controllers have full Gateway API compatibility yet. This can limit features or require choosing a new controller that natively supports Gateway.
- Learning Curve – Gateway introduces new resources (Gateways, Routes, Policies) and a role-based design. Teams need time and training to adapt their workflows.
- Feature Gaps – While Gateway API is more expressive, certain advanced use cases may still depend on vendor-specific extensions, reducing portability.
- Operational Overhead – During migration, you may need to run both Ingress and Gateway in parallel, which increases configuration complexity and resource consumption.
Best Practices for Migrating to Gateway API
- Start Small – Begin with non-critical services to test the migration workflow. This allows your team to build confidence, refine processes, and uncover challenges before moving mission-critical applications.
- Leverage Role Separation – One of Gateway API’s strengths is its role-based design. Clearly define ownership: infrastructure teams can manage Gateways, while application developers focus on Routes. This separation improves security, scalability, and team efficiency.
- Adopt GitOps Practices – Store Gateway and Route configurations in version control systems like Git. This enables collaboration, auditability, and quick rollbacks if issues arise during migration.
- Monitor and Observe – Use logging, tracing, and monitoring tools to validate routing behavior, ensure performance, and detect anomalies early.
- Stay updated – The Gateway API continues to evolve, with features like mTLS, gRPC routing, and traffic splitting being standardized. Keeping up-to-date helps your organization take advantage of new capabilities as they become stable.
Migration Strategy: Moving from Ingress to Gateway API
Migrating from Ingress to Gateway API requires planning, testing, and phased adoption. Below are the recommended steps.
Research & Planning:
- Check your current setup: Collect details about existing Ingress objects, controllers, hosts, paths, TLS configurations, annotations, and backend services.
kubectl get ingress -A -o yaml > all-ingresses.yaml
- Select a compatible Gateway controller: The Gateway API is only a specification. you’ll need an implementation. Examples include:
- NGINX
︎ NGINX Gateway Fabric
- Traefik
︎ Traefik Proxy with Gateway API
- HAProxy
︎ HAProxy kubernetes Ingress Controller with Gateway API
- Istio
︎ Istio Gateway API
Map Ingress to Gateway API
- IngressClass
︎ GatewayClass
- Ingress
︎ Gateway + HTTPRoute
- Review RBAC changes, as Gateway API introduces more granular access control.
Proof of Concept
- Deploy Gateway API resources in a non-production namespace.
- Validate routing, TLS, and service connectivity before rolling out further.
Run Both resources in Parallel
- Keep Ingress for production traffic.
- Deploy equivalent Gateway + HTTPRoute resources in parallel.
- Use staging domains (e.g.,myapp.example.com) to validate behavior.
Migrate TLS
- Unlike Ingress (which often uses annotations), Gateway API treats TLS as a first-class citizen.
- Create TLS secrets and reference them in Gateway listeners.
Handle Vendor-Specific Annotations
Ingress often relies on annotations like nginx.ingress.kubernetes.io/rewrite-target. In Gateway API, these are replaced with native fields:
- Header matching
︎ matches.headers
- Path rewriting
︎ filters.requestRedirect, filters.urlRewrite
- Traffic splitting
︎ backendRefs.weight Example:
rules:
- backendRefs:
- name: v1-service
port: 80
weight: 80
- name: v2-service
port: 80
weight: 20
Shift Production Traffic
- Update DNS to point to the Gateway load balancer.
- Monitor traffic using logs and metrics.
- Roll back to Ingress if needed (since it still runs in parallel).
Decommission Ingress
- Remove old Ingress objects.
- Uninstall the Ingress controller.
- Retain Gateway API resources as the single source of truth for service networking.
Migrating an existing Kubernetes Ingress resource to the Gateway API
Prerequisites
A running Kubernetes cluster(1.24+) having deployment, services, pods running with ingress resource.
Step 1: Install Gateway API Resources
# Install Gateway API resources
kubectl kustomize "https://github.com/nginx/nginx-gateway-fabric/config/crd/gateway-api/standard?ref=v1.5.1" | kubectl apply -f -
# Verify installation
kubectl get crd | grep gateway
Step 2: Configure NGINX Gateway Fabric
We’ll install NGINX Gateway Fabric as our Gateway API controller:
# Deploy NGINX Gateway Fabric CRDs
kubectl apply -f https://raw.githubusercontent.com/nginx/nginx-gateway-fabric/v1.6.1/deploy/crds.yaml
# Deploys NGINX Gateway Fabric with NGINX OSS using a Service type of NodePort
kubectl apply -f https://raw.githubusercontent.com/nginx/nginx-gateway-fabric/v1.6.1/deploy/nodeport/deploy.yaml
# Verify the deployment
kubectl get pods -n nginx-gateway
Let’s configure specific ports for the gateway service:
# View the nginx-gateway service
kubectl get svc -n nginx-gateway nginx-gateway -o yaml
# Update the service to expose specific nodePort values
kubectl patch svc nginx-gateway -n nginx-gateway --type='json' -p='[
{"op": "replace", "path": "/spec/ports/0/nodePort", "value": 30080},
{"op": "replace", "path": "/spec/ports/1/nodePort", "value": 30081}
]'
# Verify the service has been updated
kubectl get svc -n nginx-gateway nginx-gateway
Step 3: Create GatewayClass and Gateway Resources
let’s create the GatewayClass and Gateway resources. Save the following YAML to a file named gateway-resources.yaml:
---
apiVersion: gateway.networking.k8s.io/v1
kind: GatewayClass
metadata:
name: nginx
spec:
controllerName: gateway.nginx.org/nginx-gateway-controller
---
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: nginx-gateway
namespace: nginx-gateway
spec:
gatewayClassName: nginx # Use the gateway class that matches your controller
listeners:
- name: https
port: 443
protocol: HTTPS
hostname: myweb.k8s.local
tls:
mode: Terminate
certificateRefs:
- kind: Secret
name: web-tls-secret
namespace: web-app
allowedRoutes:
namespaces:
from: All
- name: http
port: 80
protocol: HTTP
hostname: myweb.k8s.local
allowedRoutes:
namespaces:
from: All
Note: here, in above code we have used “web-tls-secret”. Which is already being used by Ingress resource.
# Apply the YAML:
kubectl apply -f gateway-resources.yaml
# Verify the Gateway resource
kubectl get gateway -n nginx-gateway
Step 4: Create the HTTPRoute Resource
Create YAML file for HTTPRoute for http with name http.yaml.
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: web-route
namespace: web-app
spec:
parentRefs:
- name: nginx-gateway
kind: Gateway
namespace: nginx-gateway
sectionName: http
hostnames:
- myweb.k8s.local
rules:
- matches:
- path:
type: PathPrefix
value: /
backendRefs:
- name: web-service
port: 80
Create YAML file for HTTPRoute for https with name https.yaml.
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: web-route-https
namespace: web-app
spec:
parentRefs:
- name: nginx-gateway
kind: Gateway
namespace: nginx-gateway
sectionName: https
hostnames:
- myweb.k8s.local
rules:
- matches:
- path:
type: PathPrefix
value: /
backendRefs:
- name: web-service
port: 80
Note: Here, we have choose namespace=web-app at metadata of resource which is same as Deployment resource namespace.
# Apply the resource YAML:
kubectl apply -f http.yaml
kubectl apply -f https.yaml
# Verify the HTTPRoute resources
kubectl get httproute -n web-app
Step 5: Verify the Gateway API Configuration
# Check the Gateway status
kubectl describe gateway nginx-gateway -n web-app
# Check the HTTPRoute status
kubectl describe httproute web-route -n web-app
# Check if the HTTPRoute is properly bound to the Gateway
kubectl get httproute web-route -n web-app -o jsonpath='{.status.parents[0].conditions[?(@.type=="Accepted")].status}'
kubectl get httproute web-route-https -n web-app -o jsonpath='{.status.parents[0].conditions[?(@.type=="Accepted")].status}'
The output for the last command should be “True” if the HTTPRoute is properly accepted by the Gateway.
Note: If you are facing error with the listener, make sure that secret was created in the same namespace as gateway, if not, create the below reference grant.
cat <<EOF | kubectl apply -f -
apiVersion: gateway.networking.k8s.io/v1beta1
kind: ReferenceGrant
metadata:
name: allow-gateway-to-web-app-secrets
namespace: web-app
spec:
from:
- group: gateway.networking.k8s.io
kind: Gateway
namespace: nginx-gateway
to:
- group: ""
kind: Secret
name: web-tls-secret
EOF
Step 6: Test the Gateway API Configuration
Now, we are testing to see that application is accessible through the new Gateway API.
# Test the / endpoint
curl -v -H "Host: myweb.k8s.local" http://$NODE_IP:30080/
# Add the entry in /etc/hosts
# ${NODE_IP} myweb.k8s.local
# Test the / endpoint
curl -v -k https://myweb.k8s.local:30081/
Step 7: After successful Verification Remove the Old Ingress Resource
kubectl get ingress -A
kubectl delete ingress <ingress-name> -n web-app
Final Thoughts
Migrating from Ingress to Gateway API isn’t just about swapping tools. it’s about future-proofing your Kubernetes platform. With clearer roles, richer features, and fewer vendor lock-ins, Gateway API helps teams collaborate better while handling complex networking with ease. Start small, experiment, and scale gradually. Each step moves you closer to a more reliable, flexible, and modern foundation for your applications and the teams that build them.
This content originally appeared on DEV Community and was authored by kelvin manavar