Ingresses have been, since the early versions of Kubernetes, the most common way to expose applications to the outside. Although their initial design was simple and elegant, the success of Kubernetes and the growing complexity of use cases have turned Ingress into a problematic piece: limited, inconsistent between vendors, and difficult to govern in enterprise environments.
In this article, we analyze why Ingresses have become a constant source of friction, how different Ingress Controllers have influenced this situation, and why more and more organizations are considering alternatives like Gateway API.
What Ingresses are and why they were designed this way
The Ingress ecosystem revolves around two main resources:
🏷️ IngressClass
Defines which controller will manage the associated Ingresses. Its scope is cluster-wide, so it is usually managed by the platform team.
🌐 Ingress
It is the resource that developers use to expose a service. It allows defining routes, domains, TLS certificates, and little more.
Its specification is minimal by design, which allowed for rapid adoption, but also laid the foundation for current problems.
The problem: a standard too simple for complex needs
As Kubernetes became an enterprise standard, users wanted to replicate advanced configurations of traditional proxies: rewrites, timeouts, custom headers, CORS, etc. But Ingress did not provide native support for all this.
Vendors reacted… and chaos was born.
Annotations vs CRDs: two incompatible paths
Different Ingress Controllers have taken very different paths to add advanced capabilities:
📝 Annotations (NGINX, HAProxy…)
Advantages:
Flexible and easy to use
Directly in the Ingress resource
Disadvantages:
Hundreds of proprietary annotations
Fragmented documentation
Non-portable configurations between vendors
📦 Custom CRDs (Traefik, Kong…)
Advantages:
More structured and powerful
Better validation and control
Disadvantages:
Adds new non-standard objects
Requires installation and management
Less interoperability
Result? Infrastructures deeply coupled to a vendor, complicating migrations, audits, and automation.
The complexity for development teams
The design of Ingress implies two very different responsibilities:
Platform: defines IngressClass
Application: defines Ingress
But the reality is that the developer ends up making decisions that should be the responsibility of the platform area:
Certificates
Security policies
Rewrite rules
CORS
Timeouts
Corporate naming practices
This causes:
Inconsistent configurations
Bottlenecks in reviews
Constant dependency between teams
Lack of effective standardization
In large companies, where security and governance are critical, this is especially problematic.
NGINX Ingress: the decommissioning that reignited the debate
The recent decommissioning of the NGINX Ingress Controller has highlighted the fragility of the ecosystem:
This has reignited the conversation about the need for a real standard… and there appears Gateway API.
Gateway API: a promising alternative (but not perfect)
Gateway API was born to solve many of the limitations of Ingress:
Clear separation of responsibilities (infrastructure vs application)
Standardized extensibility
More types of routes (HTTPRoute, TCPRoute…)
Greater expressiveness without relying on proprietary annotations
But it also brings challenges:
Requires gradual adoption
Not all vendors implement the same
Migration is not trivial
Even so, it is shaping up to be the future of traffic management in Kubernetes.
Conclusion
Ingresses have been fundamental to the success of Kubernetes, but their own simplicity has led them to become a bottleneck. The lack of interoperability, differences between vendors, and complex governance in enterprise environments make it clear that it is time to adopt more mature models.
Gateway API is not perfect, but it moves in the right direction. Organizations that want future stability should start planning their transition.
Introduction OpenShift, Red Hat’s Kubernetes platform, has its own way of exposing services to external clients. In vanilla Kubernetes, you would typically use an Ingress resource along with an ingress controller to route external traffic to services. OpenShift, however, introduced the concept of a Route and an integrated Router (built on HAProxy) early on, before Kubernetes Ingress even existed. Today, OpenShift supports both Routes and standard Ingress objects, which can sometimes lead to confusion about when to use each and how they relate.
This article explores how OpenShift handles Kubernetes Ingress resources, how they translate to Routes, the limitations of this approach, and guidance on when to use Ingress versus Routes.
OpenShift Routes and the Router: A Quick Overview
OpenShift Routes are OpenShift-specific resources designed to expose services externally. They are served by the OpenShift Router, which is an HAProxy-based proxy running inside the cluster. Routes support advanced features such as:
Because Routes are OpenShift-native, the Router understands these features natively and can be configured accordingly. This tight integration enables powerful and flexible routing capabilities tailored to OpenShift environments.
Using Kubernetes Ingress in OpenShift (Default Behavior)
Starting with OpenShift Container Platform (OCP) 3.10, Kubernetes Ingress resources are supported. When you create an Ingress, OpenShift automatically translates it into an equivalent Route behind the scenes. This means you can use standard Kubernetes Ingress manifests, and OpenShift will handle exposing your services externally by creating Routes accordingly.
This automatic translation simplifies migration and supports basic use cases without requiring Route-specific manifests.
Tuning Behavior with Annotations (Ingress ➝ Route)
When you use Ingress on OpenShift, only OpenShift-aware annotations are honored during the Ingress ➝ Route translation. Controller-specific annotations for other ingress controllers (e.g., nginx.ingress.kubernetes.io/*) are ignored by the OpenShift Router. The following annotations are commonly used and supported by the OpenShift router to tweak the generated Route:
Purpose
Annotation
Typical Values
Effect on Generated Route
TLS termination
route.openshift.io/termination
edge · reencrypt · passthrough
Sets Route spec.tls.termination to the chosen mode.
This Ingress will be realized as a Route with edge TLS and an automatic HTTP→HTTPS redirect, using least connections balancing and a 60s route timeout. The HSTS header will be added by the router on HTTPS responses.
Limitations of Using Ingress to Generate Routes While convenient, using Ingress to generate Routes has limitations:
Missing advanced features: Weighted backends and sticky sessions require Route-specific annotations and are not supported via Ingress.
TLS passthrough and re-encrypt modes: These require OpenShift-specific annotations on Routes and are not supported through standard Ingress.
Ingress without host: An Ingress without a hostname will not create a Route; Routes require a host.
Wildcard hosts: Wildcard hosts (e.g., *.example.com) are only supported via Routes, not Ingress.
Annotation compatibility: Some OpenShift Route annotations do not have equivalents in Ingress, leading to configuration gaps.
Protocol support: Ingress supports only HTTP/HTTPS protocols, while Routes can handle non-HTTP protocols with passthrough TLS.
Config drift risk: Because Routes created from Ingress are managed by OpenShift, manual edits to the generated Route may be overwritten or cause inconsistencies.
These limitations mean that for advanced routing configurations or OpenShift-specific features, using Routes directly is preferable.
When to Use Ingress vs. When to Use Routes Choosing between Ingress and Routes depends on your requirements:
Use Ingress if:
You want portability across Kubernetes platforms.
You have existing Ingress manifests and want to minimize changes.
Your application uses only basic HTTP or HTTPS routing.
You prefer platform-neutral manifests for CI/CD pipelines.
Use Routes if:
You need advanced routing features like weighted backends, sticky sessions, or multiple TLS termination modes.
Your deployment is OpenShift-specific and can leverage OpenShift-native features.
You require stability and full support for OpenShift routing capabilities.
You need to expose non-HTTP protocols or use TLS passthrough/re-encrypt modes.
You want to use wildcard hosts or custom annotations not supported by Ingress.
In many cases, teams use a combination: Ingress for portability and Routes for advanced or OpenShift-specific needs.
Conclusion
On OpenShift, Kubernetes Ingress resources are automatically converted into Routes, enabling basic external service exposure with minimal effort. This allows users to leverage existing Kubernetes manifests and maintain portability. However, for advanced routing scenarios and to fully utilize OpenShift’s powerful Router features, using Routes directly is recommended.
Both Ingress and Routes coexist seamlessly on OpenShift, allowing you to choose the right tool for your application’s requirements.
What is the difference between an OpenShift Route and a Kubernetes Ingress?
Route is OpenShift’s native, pre-Ingress API for exposing services; Ingress is the upstream Kubernetes standard. Functionally they overlap, but Routes support OpenShift-specific TLS modes (edge, reencrypt, passthrough) natively, while Ingress is portable across any Kubernetes distribution.
Does OpenShift support standard Kubernetes Ingress?
Yes. When you create an Ingress, the OpenShift router generates the equivalent Route objects automatically, so upstream manifests work unmodified. The generated Routes are owned by the Ingress — edit the Ingress, not the Routes.
Should I use Route or Ingress in OpenShift?
Use Ingress if your manifests must stay portable across distributions or come from upstream Helm charts. Use Route when you need OpenShift-specific behavior (reencrypt TLS, passthrough) or your platform is OpenShift-only by policy. Mixing both works, but standardising on one keeps GitOps diffs sane.
How does TLS work when an Ingress generates a Route?
TLS config in the Ingress (a tls block with a certificate Secret) maps to an edge-terminated Route. For reencrypt or passthrough you need either Route objects directly or the OpenShift-specific annotation route.openshift.io/termination on the Ingress.