Beyond the first Gateway
In 16.26 you created a Gateway and a canary HTTPRoute, and fixed NotAllowedByListeners and RefNotPermitted once. This lesson is the reference you will want on call: every condition and reason, HTTPS listeners, and how a Gateway decides which routes it serves.
What you need to know already: GatewayClass, Gateway, HTTPRoute, parentRefs, allowedRoutes and ReferenceGrant (16.26-16.28), TLS Secrets (16.22), CRDs (16.26), the cert-manager lesson of this chapter.
Three roles, written into the API
infrastructure provider ships the implementation and its GatewayClass (cloud, platform vendor)
cluster operator creates Gateways: addresses, ports, TLS, which (platform team)
namespaces may attach
application developer creates routes in their own namespace (app teams)
RBAC follows the split: app teams get create on httproutes in their namespace, nothing on gateways. That is the point - a team can ship routing rules on a shared entry point without being able to break it for the others.
The current release
Gateway API ships as CRDs in two channels: standard (GA, stable, v1 fields only) and experimental (new fields first). The latest is v1.6 (v1.6.2, September 2026). In the standard channel today:
GatewayClass, Gateway, HTTPRoute, GRPCRoute v1 since 1.0 / 1.1
ReferenceGrant v1 since 1.5 (v1beta1 still served and stored)
TLSRoute, ListenerSet, CORS filter standard since 1.5
TCPRoute, UDPRoute v1 since 1.6
HTTPRoute timeouts (request, backendRequest) standard
HTTPRoute retries experimental (GEP-1731)
Install the CRDs of the channel your implementation supports; installing experimental CRDs under an implementation that only understands standard gives you fields it silently ignores.
Listeners
A Gateway's listeners are its doors:
listeners:
- name: http
port: 80
protocol: HTTP
- name: shop-https
port: 443
protocol: HTTPS
hostname: shop.lab # only requests for this name (SNI + Host)
tls:
mode: Terminate # (Passthrough exists for TLSRoute)
certificateRefs:
- kind: Secret
name: shop-tls
namespace: certs # another namespace: needs a ReferenceGrant
allowedRoutes:
namespaces:
from: Selector
selector:
matchLabels:
gateway-access: shop
kinds:
- kind: HTTPRoute
- Listeners on the same port must differ by
hostname, or the Gateway marks them Conflicted (HostnameConflict, or ProtocolConflict for HTTP vs TCP on one port). - A route attaches to a listener when the namespace is allowed, the kind is allowed, and the hostnames intersect: a listener for
shop.laband a route forpay.labnever meet (NoMatchingListenerHostname). - A route can pick one listener with
parentRefs[].sectionName(the listener's name) orport. A typo in sectionName = NoMatchingParent... or simply no attachment.
Certificates in another namespace. Platform teams keep TLS Secrets in a locked-down namespace. A Gateway may only use them if that namespace says so:
apiVersion: gateway.networking.k8s.io/v1beta1
kind: ReferenceGrant
metadata:
name: gateways-may-use-certs
namespace: certs # lives next to the Secrets
spec:
from:
- group: gateway.networking.k8s.io
kind: Gateway
namespace: gw-edge
to:
- group: ""
kind: Secret
Without it the listener reports ResolvedRefs False, RefNotPermitted and the data plane does not serve that certificate. Same object as 16.28, other direction: there an HTTPRoute pointed at a Service, here a Gateway at a Secret.
Status: the whole table
Every object has status.conditions (a route has them per parent). This is the list from the v1.6 API, with the reasons you will actually see:
GatewayClass Accepted True: Accepted. False: InvalidParameters, Unsupported. Unknown: Pending/Waiting
SupportedVersion False: UnsupportedVersion (CRDs newer/older than the controller)
Gateway Accepted False: ListenersNotValid, UnsupportedAddress, InvalidParameters
Unknown + Pending "Waiting for controller": no controller for this class
Programmed False: AddressNotAssigned (no LB address), NoResources, Invalid, Pending
Listener Accepted False: PortUnavailable, UnsupportedProtocol, UnsupportedValue
Programmed False: Invalid, Pending
ResolvedRefs False: InvalidCertificateRef, RefNotPermitted, InvalidRouteKinds
Conflicted True: HostnameConflict, ProtocolConflict
Route parent Accepted False: NotAllowedByListeners, NoMatchingListenerHostname,
NoMatchingParent, UnsupportedValue, IncompatibleFilters
ResolvedRefs False: BackendNotFound, RefNotPermitted, InvalidKind, UnsupportedProtocol
Reading order on call: GatewayClass Accepted -> Gateway Programmed -> listener ResolvedRefs/Conflicted -> route Accepted -> route ResolvedRefs. The first False is your problem.
# quick views
kubectl get gatewayclass ACCEPTED column
kubectl get gateway -A PROGRAMMED + ADDRESS
kubectl describe gateway edge -n gw-edge listeners, attachedRoutes, conditions
kubectl get httproute shop -n shop -o jsonpath='{range .status.parents[*].conditions[*]}{.type}={.status} {.reason}{"\n"}{end}'
attachedRoutes on a listener counts the routes that attached. A route you just applied that does not raise that number did not attach - check its status before anything else.
A Gateway nobody handles (a typo in gatewayClassName, or the implementation not installed) keeps the CRD's default status: Accepted Unknown, reason Pending, "Waiting for controller", and PROGRAMMED Unknown. Nothing is "wrong" with it - nobody is listening.
In an interview: "Gateway API splits Ingress into GatewayClass (the implementation), Gateway (the entry point, owned by the platform team) and routes (owned by app teams). When a route does not work I read its status: Accepted False with NotAllowedByListeners means the Gateway does not admit its namespace; ResolvedRefs False with RefNotPermitted means a missing ReferenceGrant."
What you can now do:
- write HTTP and HTTPS listeners, including certificates from another namespace
- read every Gateway, listener and route condition and name the object at fault
- tell "nobody handles this class" from "the route is not allowed"