Skip to content

For the complete documentation index, see llms.txt. Markdown versions of all docs pages are available by appending .md to any docs URL.

Page as Markdown

    

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 defaultBodyFormat field 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 mappers to 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, the defaultBodyFormat applies.

Before you begin

  1. Follow the Get started guide to install kgateway.

  2. Follow the Sample app guide to create a gateway proxy with an HTTP listener and deploy the httpbin sample app.

  3. 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.

  1. 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
    
  2. 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
  3. 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.

  1. 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
    
  2. 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"
    EOF
    Field Description
    filterCriteria for when this mapper applies. Supports statusCodeFilter, durationFilter, headerFilter, responseFlagFilter, celFilter, and compound andFilter / orFilter.
    bodyFormatOverrideJSON or text format that overrides defaultBodyFormat for matched replies.
  3. 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"
EOF

Add 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
EOF

Match 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"
EOF

Match 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"
EOF

Cleanup

You can remove the resources that you created in this guide.
kubectl delete listenerpolicy local-replies -n kgateway-system --ignore-not-found
Was this page helpful?