Ingress vs Gateway API: A Practical Comparison
You’ve spent months perfecting your NGINX Ingress configuration. The annotations are just right, traffic flows smoothly, and you’ve documented every quirk. Then someone decides you’re switching to Traefik. Or Contour. Or migrating to a service mesh that uses Gateway API. Suddenly, none of your annotations work. Some are silently ignored, others cause validation errors, and you’re left wondering why Kubernetes made this so hard.
The answer: Ingress was designed in 2015 as a minimal abstraction. It defines what you want (route traffic to services) but leaves how entirely to the controller. Every controller filled that gap differently, creating the annotation mess you’re now untangling.
Gateway API, which reached GA in 2023, takes a different approach. It’s more expressive, more portable, and designed for multi-team environments. But more powerful doesn’t always mean better for your use case. I’ve seen teams migrate to Gateway API because “it’s the future,” only to discover that their simple routing requirements worked perfectly with Ingress—they just added complexity without new capabilities.
The Fundamental Difference
The core distinction isn’t feature lists—it’s architecture. Ingress uses one resource type for everything. Gateway API uses three.
With Ingress, you create a single resource that combines routing rules, TLS configuration, and backend references. Everything lives in one YAML file, which makes simple cases straightforward. The problem is that Ingress only defines the interface—actual behavior depends on annotations that vary by controller. An annotation that works perfectly on NGINX Ingress Controller might be silently ignored by Traefik or cause validation errors on HAProxy.
# Single Ingress resource handles everything
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: api-ingress
annotations:
# These annotations only work on NGINX Ingress Controller
nginx.ingress.kubernetes.io/rewrite-target: /
nginx.ingress.kubernetes.io/ssl-redirect: "true"
spec:
ingressClassName: nginx
tls:
- hosts: ["api.example.com"]
secretName: api-tls
rules:
- host: api.example.com
http:
paths:
- path: /v1
pathType: Prefix
backend:
service:
name: api-v1
port:
number: 80Gateway API separates concerns into layers. GatewayClass defines which controller handles traffic (managed by infrastructure teams). Gateway defines listeners—ports, protocols, TLS termination (managed by platform teams). HTTPRoute defines routing rules (managed by application teams). Each layer has a clear owner and a narrow scope.
# HTTPRoute: Application team defines their routing
# Native fields for weights, filters, header matching—no annotations needed
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: api-routes
namespace: team-a # Lives in app team's namespace
spec:
parentRefs:
- name: shared-gateway
namespace: gateway-system # References platform team's Gateway
hostnames:
- "api.example.com"
rules:
- matches:
- path:
type: PathPrefix
value: /v1
backendRefs:
- name: api-v1
port: 80The critical difference: HTTPRoute uses native fields for routing configuration, not annotations. These fields work identically across any Gateway API implementation—Envoy Gateway, Istio, Contour, Kong, NGINX Gateway Fabric. Portability is built in, not bolted on.
| Aspect | Ingress | Gateway API |
|---|---|---|
| Resource types | 1 | 3+ (GatewayClass, Gateway, *Route) |
| Configuration | Annotations (not portable) | Native fields (portable) |
| Namespace scope | Same namespace only | Cross-namespace supported |
| Protocols | HTTP/HTTPS | HTTP, HTTPS, TCP, UDP, gRPC, TLS |
| Traffic splitting | Annotation-dependent | Native support |
| Role separation | No | Yes (infra/platform/app) |
Gateway API’s layered architecture also enables something Ingress can’t: application teams defining their own routes without needing access to shared infrastructure. An HTTPRoute in namespace team-a can reference a Gateway in namespace gateway-system. The Gateway controls which namespaces can attach routes through allowedRoutes, creating self-service routing with guardrails.
When to Choose Which
The decision depends on your current situation, not abstract feature comparisons.
$ Stay Updated
> One deep dive per month on infrastructure topics, plus quick wins you can ship the same day.
When to Stick with Ingress
Your routing needs are simple—host-based routing, path prefixes, TLS termination. If everything lives in one namespace (or a small number managed by one team) and your current setup works without friction, Ingress is fine. There’s no deprecation timeline. The Kubernetes networking SIG has confirmed both APIs will coexist indefinitely.
Migrating because Gateway API is “the future” wastes engineering time. I’ve watched teams spend weeks converting resources, only to end up with equivalent functionality in a more complex setup. They’d have been better served improving their deployment pipeline or writing better runbooks.
Ingress makes sense for: a single application with a few endpoints, internal tools with basic routing, microservices where one team owns everything.
When to Choose Gateway API
You need capabilities Ingress genuinely lacks. Cross-namespace routing is the clearest signal—if application teams need to self-service their routing without accessing shared infrastructure namespaces, Gateway API solves this cleanly. Native traffic splitting for canary deployments is another strong indicator. With Ingress, you’re juggling controller-specific annotations that break when you change controllers. Gateway API’s weight field on backend references just works.
Protocol requirements matter too. If you need TCP routing to databases, UDP routing to DNS servers, or gRPC routing with method-level matching, Ingress doesn’t help. Gateway API has dedicated route types for each.
Gateway API makes sense for: multi-tenant platforms, deployment pipelines with canary stages, mixed-protocol workloads, any scenario where multiple teams need independent routing control.
The Hybrid Option
You don’t have to choose one. Both APIs can run on the same cluster, even with the same controller. Many organizations use Ingress for stable legacy services and Gateway API for new platform services requiring advanced features.
Consider a concrete example: your cluster runs an internal admin dashboard that hasn’t changed in two years (Ingress works fine), a legacy API in maintenance mode (no reason to migrate), and a new public API with canary deployment requirements (Gateway API). Running both lets you use Gateway API where it adds value without touching stable workloads.
The cost is cognitive load—your team needs to know both patterns. Document which services use which API and establish conventions for new services.
Migration Reality Check
If you’ve decided Gateway API is right for your use case, understand what you’re signing up for.
The good news: Ingress and Gateway API can run in parallel. You can migrate one route at a time with instant rollback—delete the HTTPRoute and traffic falls back to Ingress. This isn’t a big-bang migration.
The conversion complexity depends entirely on your annotation usage. Standard fields (hosts, paths, TLS) convert mechanically. You can migrate a simple route in under an hour. But custom annotations need research. Each nginx.ingress.kubernetes.io/* annotation needs a Gateway API equivalent—some map to native filters, some require policy attachments, some have no direct equivalent.
The configuration-snippet annotation is the hardest. It allows arbitrary NGINX config, meaning you need to understand exactly what it’s doing and find an alternative. Sometimes there isn’t one.
Most major controllers now support Gateway API at GA level: NGINX Gateway Fabric, Envoy Gateway, Istio, Contour, Kong, and Traefik. HAProxy support is in beta. If you’re using NGINX Ingress Controller, note that Gateway API support comes through a different project—NGINX Gateway Fabric—not the original nginx-ingress.
Plan for a multi-week timeline. A typical 20-service cluster with moderate annotation usage takes 4-8 weeks for careful migration with parallel operation. Rushed migrations create incidents.
The key insight: migration is a project, not a weekend task. Budget time for annotation research, staging validation, and parallel operation. But the incremental approach means you’re never committed until you delete the old Ingress resources.
Making the Decision
Choose based on your actual problems, not industry trends. If Ingress works for your routing needs and you don’t have concrete pain points, keep using it. If you’re fighting annotation portability, need multi-team self-service routing, or require non-HTTP protocols, Gateway API solves real problems.
Download the Ingress vs Gateway API Guide
Get the complete routing playbook for API selection, migration planning, and controller-compatible implementation patterns.
What you'll get:
- Routing capability comparison matrix
- Annotation migration mapping guide
- Controller support readiness checklist
- Phased migration execution plan
If you’re not sure where to start: audit your current Ingress resources. Count the custom annotations. If you’re using mostly standard fields (hosts, paths, TLS) and everything lives in a handful of namespaces managed by one team, Ingress is probably fine. If you’re using controller-specific annotations for traffic splitting, header manipulation, or rate limiting—and especially if you’ve ever considered switching controllers—Gateway API is worth the investment.
For greenfield projects without strong opinions, Gateway API is a reasonable default. It’s mature enough for production, and starting there avoids a future migration. But “future-proofing” isn’t a good reason to migrate existing stable workloads.
Both APIs will coexist for years. Make the decision based on requirements, not hype.
Table of Contents
Share this article
Found this helpful? Share it with others who might benefit.
Share this article
Enjoyed the read? Share it with your network.