For the complete documentation index, see llms.txt. Markdown versions of all docs pages are available by appending .md to any docs URL.
Local replies
By default, Envoy returns error responses as plaintext. For APIs that serve JSON clients, this mismatch can cause parsing errors or expose internal error messages in an uncontrolled format. You can use a ListenerPolicy to configure the Envoy local reply behavior for all listeners. For example, you can set a default body format for all generated errors, or use mappers to intercept specific replies and override their status code, body, or headers.
About local replies
A local reply is any HTTP response that is generated by Envoy itself rather than forwarded from an upstream service. Common examples include:
- 404: No route matched the request
- 403: An RBAC or other policy denied the request
- 413: A request body exceeded the buffer limit
- 503: No healthy upstream endpoints
These responses are produced at the listener level, before any route or backend is involved. Because of this, you cannot use route-level filters, such as the custom response filter to intercept and manipulate these responses. Instead, you must change the format of these responses by using the localReplies field in a ListenerPolicy.
You can customize local replies in two ways:
- Default body format: Use the
defaultBodyFormatfield to define a format that you want to apply to all local replies on a listener. This setup is useful when you want all error responses to follow a consistent structure, such as returning JSON instead of plaintext for every error that Envoy generates. - Mappers: Use
mappersto match specific local replies by filter criteria, such as a status code or response flag, and apply a custom body, status code, or headers to matched replies only. Mappers are evaluated in order and the first match wins. If a mapper does not define a body format, thedefaultBodyFormatapplies.
Before you begin
-
Follow the Get started guide to install kgateway.
-
Follow the Sample app guide to create a gateway proxy with an HTTP listener and deploy the httpbin sample app.
-
Get the external address of the gateway and save it in an environment variable.
export INGRESS_GW_ADDRESS=$(kubectl get svc -n kgateway-system http -o jsonpath="{.status.loadBalancer.ingress[0]['hostname','ip']}") echo $INGRESS_GW_ADDRESS
Set a default body format
Use the defaultBodyFormat field in a ListenerPolicy resource to change the body format for all local replies on a listener. You can choose between the following formats:
text: Produces a plain string body.json: Produces a JSON object.
Choose the format based on the format that your clients expect in error responses. In both formats, you can use Envoy substitution strings to format the response body. For example, use %RESPONSE_CODE_DETAILS% to include a machine-readable error reason such as route_not_found, or %RESPONSE_CODE% to get the HTTP status code.
-
Send a request to the gateway for a hostname that has no matching route. Verify that you get back a 404 response without a body.
curl -vi http://$INGRESS_GW_ADDRESS:8080/ \ -H "host: no-such-host.example.com"Example output:
< HTTP/1.1 404 Not Found < content-length: 0 -
Create a ListenerPolicy with a
defaultBodyFormat.kubectl apply -f- <<EOF apiVersion: gateway.kgateway.dev/v1alpha1 kind: ListenerPolicy metadata: name: local-replies namespace: kgateway-system spec: targetRefs: - group: gateway.networking.k8s.io kind: Gateway name: http default: httpSettings: localReplies: defaultBodyFormat: text: "error %RESPONSE_CODE%: %RESPONSE_CODE_DETAILS%\n" EOF -
Repeat the request. Verify that the response now includes a formatted body.
curl -vi http://$INGRESS_GW_ADDRESS:8080/ \ -H "host: no-such-host.example.com"Example output for the JSON format:
< HTTP/1.1 404 Not Found < content-type: application/json ... {"message":"route_not_found","status":"404"}
Filter errors with mappers
Use mappers to match specific local replies by filter criteria and override their status code, body, or headers.
The following example adds a JSON body to every 404 reply where a route could not be found. By default, Envoy generates a 404 error without a body when no route matches. The mapper intercepts that reply and returns a structured JSON response instead.
-
Send a request to the httpbin app with a hostname that has no matching route. Verify that you get back a 404 HTTP response without a body.
curl -vi http://$INGRESS_GW_ADDRESS:8080/ \ -H "host: no-such-host.example.com"Example output:
< HTTP/1.1 404 Not Found < content-type: application/json -
Update the ListenerPolicy to add a mapper and custom JSON body for 404 replies.
kubectl apply -f- <<EOF apiVersion: gateway.kgateway.dev/v1alpha1 kind: ListenerPolicy metadata: name: local-replies namespace: kgateway-system spec: targetRefs: - group: gateway.networking.k8s.io kind: Gateway name: http default: httpSettings: localReplies: mappers: - filter: statusCodeFilter: op: EQ value: 404 bodyFormatOverride: json: error: "not found" code: "404" EOFField Description filterCriteria for when this mapper applies. Supports statusCodeFilter,durationFilter,headerFilter,responseFlagFilter,celFilter, and compoundandFilter/orFilter.bodyFormatOverrideJSON or text format that overrides defaultBodyFormatfor matched replies. -
Repeat the request. Verify that you now see the custom response body.
curl -vi http://$INGRESS_GW_ADDRESS:8080/ \ -H "host: no-such-host.example.com"Example output:
< HTTP/1.1 404 Not Found < content-type: application/json ... {"error":"not found","code":"404"}
Other configurations
Review other common mapper configurations.
Remap a status code
Use the statusCode field to change the HTTP status code that is returned to the client. In the following example, you convert 403 error responses to a 404 error response.
kubectl apply -f- <<EOF
apiVersion: gateway.kgateway.dev/v1alpha1
kind: ListenerPolicy
metadata:
name: local-replies
namespace: kgateway-system
spec:
targetRefs:
- group: gateway.networking.k8s.io
kind: Gateway
name: http
default:
httpSettings:
localReplies:
mappers:
- filter:
statusCodeFilter:
op: EQ
value: 403
statusCode: 404
bodyFormatOverride:
json:
error: "not found"
code: "404"
EOFAdd response headers
Use the headers field to add or modify headers on the local reply. This is useful for signaling error context to downstream clients or proxies. Note that removing headers is not supported.
kubectl apply -f- <<EOF
apiVersion: gateway.kgateway.dev/v1alpha1
kind: ListenerPolicy
metadata:
name: local-replies
namespace: kgateway-system
spec:
targetRefs:
- group: gateway.networking.k8s.io
kind: Gateway
name: http
default:
httpSettings:
localReplies:
mappers:
- filter:
statusCodeFilter:
op: EQ
value: 503
headers:
set:
- name: x-error-source
value: envoy-local-reply
EOFMatch by response flag
Use the responseFlagFilter to match replies with a specific Envoy response flag. For example, match UO (upstream overflow) to intercept circuit-breaker rejections and return a custom body.
kubectl apply -f- <<EOF
apiVersion: gateway.kgateway.dev/v1alpha1
kind: ListenerPolicy
metadata:
name: local-replies
namespace: kgateway-system
spec:
targetRefs:
- group: gateway.networking.k8s.io
kind: Gateway
name: http
default:
httpSettings:
localReplies:
mappers:
- filter:
responseFlagFilter:
flags:
- UO
bodyFormatOverride:
json:
error: "service unavailable"
reason: "circuit breaker open"
EOFMatch multiple conditions
Use the andFilter to list multiple conditions that all must be true before a mapper applies. The following example matches 503 replies that are also associated with an upstream overflow flag.
kubectl apply -f- <<EOF
apiVersion: gateway.kgateway.dev/v1alpha1
kind: ListenerPolicy
metadata:
name: local-replies
namespace: kgateway-system
spec:
targetRefs:
- group: gateway.networking.k8s.io
kind: Gateway
name: http
default:
httpSettings:
localReplies:
mappers:
- filter:
andFilter:
- statusCodeFilter:
op: EQ
value: 503
- responseFlagFilter:
flags:
- UO
bodyFormatOverride:
json:
error: "circuit breaker open"
code: "503"
EOFCleanup
You can remove the resources that you created in this guide.kubectl delete listenerpolicy local-replies -n kgateway-system --ignore-not-found