Skip to main content

Ingress (NGINX) to Gateway API Migration

The community-maintained Ingress NGINX controller (kubernetes/ingress-nginx) is being officially retired by the Kubernetes SIG Network and Security Response Committee.

  • The project has moved to best-effort maintenance mode
  • Only bug fixes and security updates will continue
  • Support is expected to continue until March 2026

In light of this, the Kubernetes community has been actively developing and promoting the Gateway API as the modern, extensible successor to Ingress for Kubernetes traffic management.

Adapting to this shift, as part of the Witboost v2.9.0 upgrade or installation, you can now choose to deploy either the Gateway API or continue using Ingress for traffic management. By default, Ingress remains enabled and Gateway is disabled to preserve existing behavior. If you wish to enable Gateway API, ensure you complete the required pre-requisites outlined below before switching from Ingress to Gateway as the default

Configure Gateway API

This can be controlled via Witboost Helm chart's configuration values.

global:
# -- Ingress Configuration for all components
ingress:
# -- Enable the creation of Ingress resources for all components
create: false

# -- Gateway Configuration for all components
gateway:
# -- Enable the creation of Gateway resources for all components
create: true
# -- Reuse an existing Gateway instead of creating one. `gateway.useExisting` has precedence over `gateway.create`
useExisting: false
# -- Name of the Gateway to use
name: witboost-gateway
# -- Namespace where the existing Gateway lives. Only set when useExisting: true
# Avoid setting this when create: true (Default value: "")
#Reason: the Gateway is always created in the Helm release namespace, but sub-charts still read this field for HTTPRoute parentRefs and if set could cause routes to reference a Gateway at the wrong address and fail to attach.
namespace: ""
# -- (Optional) Name of a specific listener on the Gateway that all Witboost HTTPRoutes should bind to.
# Leave empty when create: true — Witboost's per-component listeners are matched by hostname automatically.
# Set this only with useExisting: true to isolate Witboost routes to a dedicated listener on a shared Gateway.
sectionName: ""
# -- TLS termination mode. 'terminate': Gateway handles TLS (requires cert-manager or pre-existing Secrets).
# 'external': TLS terminated upstream by a cloud LB or proxy; Gateway uses plain HTTP listeners.
tlsMode: "terminate"
# -- The GatewayClass name provided by your installed Gateway controller (e.g. nginx, istio, cilium, kong).
# Only relevant when create: true. Not used when useExisting: true — the existing Gateway manages its own class.
gatewayClassName: nginx
# -- Annotations for the Gateway resource. The cert-manager.io/cluster-issuer annotation is only
# used when cert-manager is installed with tlsMode: terminate. Remove it if not using cert-manager.
annotations:
cert-manager.io/cluster-issuer: <your-gateway-cluster-issuer>
# -- Additional custom labels for the Gateway
labels:
app.kubernetes.io/name: shared-gateway
app.kubernetes.io/part-of: platform

warning

Before applying this configuration, make sure that you have read the following pre-requisites.

Pre-requisites

This section of document outlines the mandatory pre-requisites and configuration updates required when choosing to deploy Kubernetes Gateway API instead of traditional Ingress as part of Witboost deployment upgrade/installation.

Install Gateway API CRDs

Gateway API resources are not installed by default in Kubernetes clusters.

Follow the official installation guide: https://gateway-api.sigs.k8s.io/guides/getting-started/#installing-gateway-api

Typical Installation (Example: v1.1.0)

Standard CRDs:

kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.1.0/standard-install.yaml

Experimental CRDs (required by some controllers):

kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.1.0/experimental-install.yaml
Verify Installation
kubectl get crds | grep gateway.networking.k8s.io

Expected resources include:

  • gateways.gateway.networking.k8s.io
  • httproutes.gateway.networking.k8s.io
  • referencegrants.gateway.networking.k8s.io
  • etc.

Install a Gateway Controller

Installing CRDs alone is not sufficient. You must install a Gateway controller that implements the Gateway API specification.

Official controller installation guidance: https://gateway-api.sigs.k8s.io/guides/#installing-a-gateway-controller

This page lists multiple supported controllers (NGINX, Istio, Kong, Traefik, etc.) and their official documentation links.

Example Controller Deployment

For demonstration, we use NGINX Gateway Fabric deployed via Helm (OCI chart).

helm install nginx-gateway \
oci://ghcr.io/nginxinc/charts/nginx-gateway-fabric \
--namespace gatewayapi \
--create-namespace

After installation, verify controller pods:

kubectl get pods -n gatewayapi

TLS Certificate Configuration

When Witboost creates the Gateway (create: true), TLS behaviour is controlled by the tlsMode value. Three deployment paths are supported depending on your environment.

When using useExisting: true, TLS is managed entirely by the pre-existing Gateway and its upstream infrastructure (e.g. a cloud load balancer). The tlsMode field does not apply in that case.

Option A — cert-manager (automatic certificate provisioning)

If cert-manager is installed in your cluster, it can automatically provision and renew TLS certificates for each Gateway listener. Three things must be true:

  1. tlsMode: terminate (the default) — Witboost creates HTTPS listeners on the Gateway with certificateRefs pointing to the TLS Secrets cert-manager will provision.
  2. global.gateway.annotations.cert-manager.io/cluster-issuer — set to the name of a Gateway-capable ClusterIssuer. This must be a separate ClusterIssuer from the one used for Ingress, configured with an HTTP-01 solver of type gatewayHTTPRoute (not ingress). The ClusterIssuer name must also match globals.certManager.gatewayClusterIssuer in your values file.
  3. cert-manager v1.15+ with Gateway API support enabled.

cert-manager reads the annotation on the Gateway resource and automatically creates TLS Secrets (one per component) in the same namespace as the Gateway. The Secret names it creates match the defaults listed in the Option B table above. See the cert-manager setup section below for full configuration steps.

‼️ If you are using cert-manager and/or external-dns, additional steps are required

Upgrade cert-manager (Enable Gateway Support)

To allow cert-manager to monitor Gateway resources and automatically generate certificates, the Gateway API feature must be enabled.

By default, cert-manager watches Ingress resources. For Gateway API deployments, it must:

  • Watch Gateway resources
  • Use the Gateway shim
  • Support Gateway-based HTTP01 solvers
Example Upgrade Command
helm upgrade cert-manager jetstack/cert-manager \
--namespace cert-manager \
--set installCRDs=false \
--set config.apiVersion="controller.config.cert-manager.io/v1alpha1" \
--set config.kind="ControllerConfiguration" \
--set config.enableGatewayAPI=true \
--version v1.19.x
  • When using Helm (as in the example in this document), you must deploy at least cert-manager v1.15 or later in order to use:

    --set config.apiVersion="controller.config.cert-manager.io/v1alpha1" \
    --set config.kind="ControllerConfiguration" \
    --set config.enableGatewayAPI=true

    This Helm configuration enables the Gateway API watcher in the cert-manager controller.

  • Versions earlier than v1.15 do not natively support this configuration and will not function as expected for Gateway API certificate provisioning.

💡 In practice, using recent stable versions (e.g., v1.19.x or newer) ensures better compatibility with today's Gateway API CRDs and ACME HTTP-01 challenges via Gateway resources.

Upgrade / Configure External-DNS

External-DNS must be configured to detect Gateway HTTPRoute resources, not just Ingress. Gateway API uses HTTPRoute objects instead of Ingress for routing. Without this update DNS records will not be created for Gateway-managed routes.

Example Upgrade Command (AWS Cloud Provider)

helm upgrade --install external-dns external-dns/external-dns \
--namespace <namespace> \
--set provider=aws \
--set region=<region> \
--set 'sources[0]=gateway-httproute' \
--set 'sources[1]=ingress' \
--set 'domainFilters[0]=<domain>'

Gateway-specific ClusterIssuer

When using Gateway API, it is recommended that a separate ClusterIssuer is created with a Gateway-based solver.

Why Separate Issuer?

Traditional Ingress solver:

http01:
ingress:

Gateway-specific solver:

http01:
gatewayHTTPRoute:

These two solver types are not interchangeable.


Gateway ClusterIssuer Example

note

The example below uses an ACME-based issuer (e.g. Let's Encrypt or a compatible ACME server). If you use a different cert-manager issuer type — such as a self-signed issuer, an enterprise/internal CA, or HashiCorp Vault — the spec will differ. Refer to the cert-manager issuer documentation for your specific issuer type. The requirement that applies to all Gateway API issuers is that the solver must use gatewayHTTPRoute instead of ingress.

Below is a minimal example of a Gateway API–based ClusterIssuer in cert-manager using ACME (e.g., Let's Encrypt) with an HTTP-01 solver via Gateway API.

apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-gateway
spec:
acme:
email: <admin@example.com>
server: https://acme-v02.api.letsencrypt.org/directory
privateKeySecretRef:
name: letsencrypt-gateway
solvers:
- http01:
gatewayHTTPRoute:
parentRefs:
- name: witboost-gateway
namespace: <gateway-namespace>
kind: Gateway

Important Notes

  • parentRefs.name must match your deployed Gateway.
  • parentRefs.namespace must match the namespace where your Gateway is deployed.
  • Ensure Gateway listener supports HTTP (port 80) for ACME challenge.

Option B — Pre-existing TLS Secrets (without cert-manager)

If cert-manager is not installed, you must manually create Kubernetes kubernetes.io/tls Secrets containing your certificates before running helm install or helm upgrade. The Gateway controller resolves these Secrets at startup — if a referenced Secret is missing, the listener will fail to programme and traffic will not flow, unlike Ingress which falls back gracefully.

Default Secret Names and Hostnames

The following table lists the default Secret name each Gateway listener expects and the hostname the certificate must be valid for. Hostnames are derived from your globals.domain and globals.uiUrl values, so you know exactly what SANs to include when generating your certificates.

ComponentDefault TLS Secret NameOverride values.yaml keyDefault Hostname
UI / Backendgateway-ui-tlsui.gateway.tls.secretNameHost portion of globals.uiUrl
Hasuragateway-hasura-tlshasura.gateway.tls.secretNamehasura.<globals.domain>
Documentationgateway-docs-tlsdocumentation.gateway.tls.secretNamedocs.<globals.domain>
Governance Platformgateway-governance-tlsgovernance-platform.gateway.tls.secretNamewcg.<globals.domain>
Wittygateway-witty-tlswitty.gateway.tls.secretNamewitty.<globals.domain>
MCP Servergateway-mcpserver-tlswitboost-mcp-server.gateway.tls.secretNamewitboost-mcp-server.<globals.domain>

The subdomain prefix in each hostname is the service name by default (e.g. hasura, docs, wcg). It can be overridden per component using the <component>.gateway.subdomain value (e.g. hasura.gateway.subdomain).

You can override the default Secret name for any component using the corresponding tls.secretName key, which is useful when your certificates are already stored under different names.

Example: creating a Secret from an existing certificate

kubectl create secret tls gateway-ui-tls \
--cert=path/to/tls.crt \
--key=path/to/tls.key \
--namespace <witboost-release-namespace>

All Secrets must reside in the same namespace as the Witboost Helm release (the namespace where the Gateway resource is created).

Once Secrets are in place, remove or empty the cert-manager.io/cluster-issuer annotation from global.gateway.annotations:

global:
gateway:
tlsMode: "terminate"
annotations: {}

Option C — External TLS termination (cloud load balancer or reverse proxy)

If TLS is terminated upstream — for example by an AWS ALB, GCP Load Balancer, Azure Application Gateway, or an external nginx/Envoy proxy — the Gateway inside the cluster only needs to handle plain HTTP. Set tlsMode: external:

global:
gateway:
create: true
tlsMode: "external"
annotations: {} # cert-manager annotation not needed

With tlsMode: external:

  • All Gateway listeners use protocol: HTTP on port 80 — no TLS Secrets are needed in Kubernetes at all
  • The cloud LB or proxy terminates HTTPS externally and forwards plain HTTP to the cluster
  • HTTPRoutes are unaffected — they are protocol-agnostic and continue to work in both modes

Using an Existing Gateway – Hostname & Certificate Considerations

If the cluster is already configured with an existing Gateway, you can configure Witboost to reuse it without creating a new one by setting useExisting: true. Witboost will create HTTPRoutes that attach to the named Gateway.

When useExisting: true, Witboost does not create or manage the Gateway itself. The fields create, tlsMode, gatewayClassName, annotations, and labels are not used. The following fields control how Witboost attaches to the existing Gateway:

FieldPurpose
nameName of the existing Gateway to attach HTTPRoutes to
namespaceNamespace where the existing Gateway lives. Set this when the Gateway is in a different namespace from the Witboost release (e.g. gateway-system). Leave empty if both are in the same namespace.
sectionName(Optional) Name of a specific listener within the Gateway. Use this when the Gateway has multiple listeners shared across teams or products, and you want all Witboost HTTPRoutes to bind exclusively to one designated listener. Leave empty to attach to all matching listeners.

The namespace and sectionName values are propagated to every Witboost sub-chart HTTPRoute's parentRefs, so all Witboost routes target the correct Gateway and listener automatically.

namespace has no effect when create: true

When Witboost creates the Gateway (create: true), it is always deployed into the Helm release namespace — the namespace field is ignored. Sub-charts still read namespace when rendering their HTTPRoute parentRefs, so setting a non-empty namespace alongside create: true causes every HTTPRoute to reference a Gateway in the wrong namespace and fail to attach. Only set namespace when useExisting: true.

Example — Gateway in a separate namespace with a dedicated Witboost listener:

global:
gateway:
useExisting: true
name: shared-cluster-gateway
namespace: gateway-system # Gateway lives here; Witboost release is in a different namespace
sectionName: witboost-https # Optional: bind only to this named listener

Cross-namespace routing — allowedRoutes requirement

HTTPRoutes are always created in the Witboost Helm release namespace, not in the Gateway's namespace. This is by design in the Gateway API specification: routes belong to the application team's namespace, the Gateway belongs to the infrastructure team's namespace.

When the Gateway and the Witboost release are in different namespaces, the Gateway controller enforces an explicit allowlist. The default policy for every Gateway listener is:

allowedRoutes:
namespaces:
from: Same # only routes from the Gateway's own namespace

With this default, routes from the Witboost namespace are silently rejected — the HTTPRoute status shows Accepted: False with reason NotAllowedByListeners. You must configure every listener on the pre-existing Gateway to accept routes from the Witboost namespace before deploying.

Two options:

Option 1 — Allow all namespaces (simpler):

listeners:
- name: <listener-name>
...
allowedRoutes:
namespaces:
from: All

Option 2 — Allow only the Witboost namespace (tighter):

listeners:
- name: <listener-name>
...
allowedRoutes:
namespaces:
from: Selector
selector:
matchLabels:
kubernetes.io/metadata.name: <witboost-release-namespace>

This allowedRoutes configuration must be applied to every listener on the Gateway that Witboost HTTPRoutes will attach to. When sectionName is set, Witboost HTTPRoutes pin themselves to that one named listener and ignore all others — so only that listener needs the change. When sectionName is empty, HTTPRoutes try to attach to every compatible listener (matching hostname and protocol), so all of them must allow the Witboost namespace.

note

allowedRoutes.namespaces is defined in the Gateway API specification (gateway.networking.k8s.io/v1) and is enforced by all conformant Gateway controllers — NGINX Gateway Fabric, Istio, Cilium, Kong, Traefik, and others. The cross-namespace behaviour described here is universal. Some controllers additionally support layered policies (e.g. Istio AuthorizationPolicy, Cilium NetworkPolicy) that may impose further restrictions; consult your controller's documentation if routes are still rejected after configuring allowedRoutes.

TLS for pre-existing Gateways

When reusing an existing Gateway, Witboost does not create or manage TLS certificates or Secrets for that Gateway — its listeners and certificates are already configured and managed externally. Witboost only creates HTTPRoutes that attach to it.

Reusing a Pre-existing Gateway with cert-manager

When reusing an existing Gateway where cert-manager manages certificates, additional care must be taken around hostname configuration and certificate issuance, specifically when using the HTTP-01 solver.

Gateway Listener Hostname Is Mandatory for cert-manager

Although the hostname field in a Gateway listener is optional for routing purposes:

cert-manager requires a non-empty hostname on TLS listeners to issue certificates.

If the Gateway listener does not define a hostname:

  • cert-manager will not issue certificates
  • TLS automation via HTTP-01 will fail
Hostname Matching Rules (Gateway ↔ HTTPRoute)

For an HTTPRoute to attach to a Gateway:

  • At least one HTTPRoute hostname must match the Gateway listener hostname.
  • Non-matching hostnames are ignored.
  • If none match → the route is not accepted.

Example:

If Gateway listener:

*.example.com

Then valid HTTPRoute hostnames include:

  • test.example.com
  • foo.example.com

But non-matching domains are ignored.

Wildcard Hostname Strategy – Trade-offs

One possible approach when reusing an existing Gateway is:

hostname: *.example.com

This simplifies route attachment across multiple subdomains.

However for ACME-based certificate authorities (e.g., Let’s Encrypt):

  • Wildcard certificates require DNS-01 validation
  • HTTP-01 validation does not support wildcards

This DNS-01 requirement applies to all ACME-based issuers.

Note : Other Certificate Authorities

Some environments may use traditional or enterprise public CAs that may:

  • Use different validation mechanisms
  • Allow wildcard certificate issuance outside ACME constraints
warning

Because customer CA choices may vary, behavior cannot be universally predicted. If customers are able to generate and manage wildcard certificates in their environment without issue, they may use a wildcard hostname on their Gateway. However, wildcard + DNS-01 approach has NOT been tested as of Witboost v2.10.0.