Troubleshooting Gateway API: Common Issues and Fixes
The Kubernetes Gateway API has become the standard way to configure ingress and service mesh traffic in modern clusters. However, because it introduces a multi-resource model — Gateways, GatewayClasses, HTTPRoutes, TLSRoutes, and more — troubleshooting can feel overwhelming when something does not work as expected. This tutorial walks you through the most common issues developers encounter when adopting Gateway API, explains why they happen, and shows you how to fix them with practical commands and configuration examples.
What Is the Gateway API?
Gateway API is a set of Kubernetes CRDs that provide a more expressive, role-oriented, and portable alternative to the older Ingress resource. Instead of a single Ingress object, the model is split into three layers:
- GatewayClass — defines which controller implements a category of Gateways (similar to a StorageClass).
- Gateway — declares the actual listener configuration (ports, protocols, TLS).
- Route objects (HTTPRoute, TLSRoute, TCPRoute, GRPCRoute, UDPRoute) — attach to a Gateway and describe how traffic should be routed to backends.
This separation is powerful, but it also means a single broken link in the chain can prevent traffic from flowing. Understanding where to look is half the battle.
Why Troubleshooting Matters
Unlike Ingress, where a misconfiguration usually surfaces as a 404 or 503, Gateway API failures can be silent. A Route may be accepted by the controller but never produce a valid backend binding. A Gateway may report Programmed=True while listeners are actually down. Without a systematic approach, you can spend hours guessing. The fixes below follow the same diagnostic flow used by maintainers of controllers like Envoy Gateway, Istio, and the NGINX Gateway Fabric.
1. Gateway Stuck in "Not Accepted" State
The most common first symptom is a Gateway that never becomes ready. The status.conditions field is your primary diagnostic tool. Start by inspecting it:
kubectl describe gateway my-gateway -n production
Look for the Accepted and Programmed conditions. A typical failure looks like this:
Conditions:
Type Status Reason Message
Accepted False InvalidParameters listener "https" references missing TLS secret
Programmed False Pending waiting for controller to reconcile
Common Causes
- The
gatewayClassNamereferences a GatewayClass that does not exist or has no controller backing it. - A TLS listener references a Secret in a different namespace without a ReferenceGrant.
- The controller pod is not running or is watching the wrong namespace.
Fix: Verify the GatewayClass and Controller
kubectl get gatewayclass
kubectl get pods -n gateway-system
If the GatewayClass exists but its controllerName does not match the controller you deployed, the Gateway will never be reconciled. For example, Envoy Gateway uses gateway.envoyproxy.io/gatewayclass-controller. Make sure your Gateway references the correct class:
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: my-gateway
namespace: production
spec:
gatewayClassName: envoy-gateway
listeners:
- name: http
protocol: HTTP
port: 80
2. HTTPRoute Not Receiving Traffic
Your Gateway is healthy, but requests to your domain return 404 or never reach the backend. The issue is almost always in the attachment between the HTTPRoute and the Gateway.
Diagnosing with Route Status
kubectl get httproute my-route -n production -o yaml
Check the parents section in status. A healthy Route shows:
parents:
- controllerName: gateway.envoyproxy.io/gatewayclass-controller
conditions:
- type: Accepted
status: "True"
reason: Accepted
- type: ResolvedRefs
status: "True"
reason: ResolvedRefs
If Accepted is False, the Route is not attached. The most frequent cause is a mismatch in the parentRefs section.
Fix: Correct parentRefs and SectionName
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: my-route
namespace: production
spec:
parentRefs:
- name: my-gateway
namespace: production
sectionName: http
hostnames:
- api.example.com
rules:
- matches:
- path:
type: PathPrefix
value: /
backendRefs:
- name: api-service
port: 8080
Common mistakes to check:
- The
namespaceinparentRefsmust match where the Gateway lives. Cross-namespace references require a ReferenceGrant. - If
sectionNameis set, it must exactly match a listenernameon the Gateway. A typo here silently breaks attachment. - The
hostnamesmust overlap with the listener hostname. If the listener specifiesapi.example.combut the Route useswww.example.com, no rules will match.
3. Cross-Namespace ReferenceGrant Issues
Gateway API intentionally blocks cross-namespace references unless a ReferenceGrant explicitly permits them. This is a security feature, not a bug. If your Route in namespace frontend references a Gateway in gateway-system, you need a grant.
Symptom
The Route status shows ResolvedRefs=False with reason RefNotPermitted.
Fix: Create a ReferenceGrant
apiVersion: gateway.networking.k8s.io/v1beta1
kind: ReferenceGrant
metadata:
name: allow-frontend-to-gateway
namespace: gateway-system
spec:
from:
- group: gateway.networking.k8s.io
kind: HTTPRoute
namespace: frontend
to:
- group: gateway.networking.k8s.io
kind: Gateway
Note that the ReferenceGrant must live in the target namespace — the namespace of the resource being referenced, not the namespace of the resource making the reference. This is the single most common mistake developers make.
The same pattern applies to TLS certificate Secrets. If a Gateway in gateway-system references a TLS Secret in certs, the grant goes in the certs namespace:
apiVersion: gateway.networking.k8s.io/v1beta1
kind: ReferenceGrant
metadata:
name: allow-gateway-tls
namespace: certs
spec:
from:
- group: gateway.networking.k8s.io
kind: Gateway
namespace: gateway-system
to:
- group: ""
kind: Secret
4. TLS Listener Not Terminating HTTPS
When HTTPS requests fail with certificate errors or connection resets, the problem usually lies in the TLS configuration block of the listener.
Common Causes
- The referenced Secret does not exist or is in the wrong namespace.
- The Secret type is not
kubernetes.io/tls. - The certificate does not cover the hostname used in the request.
- The
modeis set toPassthroughwhen you intendedTerminate.
Fix: Validate the Secret and Listener
kubectl get secret tls-cert -n production -o jsonpath='{.type}'
The output must be kubernetes.io/tls. A generic Opaque Secret will not work. Verify the listener configuration:
listeners:
- name: https
protocol: HTTPS
port: 443
tls:
mode: Terminate
certificateRefs:
- kind: Secret
name: tls-cert
namespace: production
hostname: api.example.com
If you are using cert-manager to issue certificates, confirm the Certificate resource has a Ready=True condition before expecting the Gateway to serve traffic:
kubectl get certificate tls-cert -n production
5. BackendRefs Pointing to Non-Existent Services
A Route can be Accepted by the Gateway but still fail to route if the backend Service does not exist or has no matching port. The ResolvedRefs condition catches this.
Diagnosis
kubectl get httproute my-route -n production \
-o jsonpath='{.status.parents[*].conditions[?(@.type=="ResolvedRefs")]}'
A failure shows status: False with reason BackendNotFound or InvalidKind.
Fix: Verify the Service Exists and the Port Matches
kubectl get svc api-service -n production -o wide
Ensure the port in backendRefs matches a port defined on the Service, not a named port string. Gateway API requires the integer port number:
backendRefs:
- name: api-service
port: 8080
weight: 100
If you are using a ServiceImport from a multi-cluster service mesh, confirm the GatewayClass controller supports that backend type. Not all controllers do.
6. Controller Logs and Debugging
When status conditions do not reveal the problem, controller logs are the next stop. Each controller has its own logging conventions, but the general approach is the same.
Envoy Gateway
kubectl logs -n envoy-gateway-system -l control-plane=envoy-gateway --tail=200
NGINX Gateway Fabric
kubectl logs -n nginx-gateway -l app=nginx-gateway --tail=200
Istio Gateway API
kubectl logs -n istio-system -l app=istiod --tail=200 | grep gateway
Look for reconciliation errors, webhook rejections, or RBAC denials. A common log pattern is failed to reconcile Gateway followed by a specific reason. Increase log verbosity if needed:
kubectl -n envoy-gateway-system set env deployment/envoy-gateway \
GATEWAY_CONTROLLER_LOG_LEVEL=debug
7. Webhook Admission Errors
Gateway API installs validating and mutating webhooks. If the webhook service is unavailable, applying any Gateway API resource will fail with an error like:
Error from server (InternalError): error when creating "gateway.yaml":
Internal error occurred: failed calling webhook "validate.gateway.networking.k8s.io"
Fix: Check the Webhook Service and Certificates
kubectl get validatingwebhookconfigurations
kubectl get pods -n gateway-system
kubectl get svc -n gateway-system
Ensure the webhook pod is running and the certificate mounted in the webhook configuration matches the serving certificate. If you installed Gateway API with Helm, a failed cert rotation is a frequent culprit. Restart the controller pod to force re-registration:
kubectl rollout restart deployment gateway-controller -n gateway-system
8. Best Practices for Avoiding Common Pitfalls
Most Gateway API issues stem from a small set of recurring mistakes. Adopt these practices to minimize troubleshooting time:
- Always check status conditions first. The
Accepted,Programmed, andResolvedRefsconditions tell you exactly which layer is broken. - Use consistent naming. Listener names, section names, and hostnames must align across Gateways and Routes. Treat them as a contract.
- Namespace ReferenceGrants deliberately. Put them in the target namespace and scope them as tightly as possible to specific kinds and namespaces.
- Validate TLS Secrets before referencing them. Use
kubectl get secret <name> -o jsonpath='{.type}'to confirm the type iskubernetes.io/tls. - Pin your GatewayClass to one controller. Running multiple controllers in a cluster is fine, but each GatewayClass must map to exactly one
controllerName. - Use GitOps with dry-run validation. Run
kubectl apply --dry-run=serveragainst a cluster with the webhooks installed to catch admission errors before they reach production. - Monitor controller logs proactively. Ship controller logs to your observability stack and alert on reconciliation failures.
9. A Quick Diagnostic Checklist
When traffic is not flowing, work through this checklist in order:
- Is the Gateway
Accepted=TrueandProgrammed=True? - Does the HTTPRoute show
Accepted=TrueandResolvedRefs=True? - Do the
parentRefsnamespace and sectionName match the Gateway? - Do the Route hostnames overlap with the listener hostname?
- If cross-namespace, does a ReferenceGrant exist in the target namespace?
- Does the backend Service exist with a matching port number?
- For HTTPS, is the TLS Secret present, correctly typed, and valid for the hostname?
- Are the controller pods running and healthy?
Following this sequence will resolve the vast majority of Gateway API issues without needing to dig into controller internals.
Conclusion
Troubleshooting the Kubernetes Gateway API becomes straightforward once you internalize its layered model and learn to read status conditions as your primary signal. Most failures boil down to mismatched references, missing ReferenceGrants, invalid TLS Secrets, or backend Services that do not exist. By systematically checking the Accepted, Programmed, and ResolvedRefs conditions, verifying cross-namespace grants, and inspecting controller logs when status is silent, you can quickly pinpoint and resolve issues. As the Gateway API ecosystem matures and more controllers implement the standard conformance tests, the debugging experience will only improve — but the fundamentals covered in this tutorial will remain the backbone of any effective troubleshooting workflow.