Response headers

Use the ResponseHeaderModifier filter to add, append, overwrite, or remove headers from a response before it is sent back to the client.

For more information, see the HTTPHeaderFilter specification.

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  
    kubectl port-forward deployment/http -n kgateway-system 8080:8080

Add response headers

Add headers to incoming requests before they are sent back to the client. If the response already has the header set, the value of the header in the ResponseHeaderModifier filter is appended to the value of the header in the response.

  1. Set up a header modifier that adds a my-response: hello response header. Choose between the HTTPRoute for a Gateway API-native way, or TrafficPolicy for more flexible attachment options such as a gateway-level policy.

    kubectl apply -f- <<EOF
    apiVersion: gateway.networking.k8s.io/v1
    kind: HTTPRoute
    metadata:
      name: httpbin-headers
      namespace: httpbin
    spec:
      parentRefs:
      - name: http
        namespace: kgateway-system
      hostnames:
        - headers.example
      rules:
        - filters:
            - type: ResponseHeaderModifier
              responseHeaderModifier:
                add: 
                - name: my-response
                  value: hello
          backendRefs:
            - name: httpbin
              port: 8000
    EOF
    Setting Description
    spec.parentRefs The name and namespace of the gateway that serves this HTTPRoute. In this example, you use the http gateway that was created as part of the get started guide.
    spec.rules.filters.type The type of filter that you want to apply to incoming requests. In this example, the ResponseHeaderModifier filter is used.
    spec.rules.filters.responseHeaderModifier.add The name and value of the response header that you want to add.
    spec.rules.backendRefs The backend destination you want to forward traffic to. In this example, all traffic is forwarded to the httpbin app that you set up as part of the get started guide.

    Note: The steps in this section use the Envoy-based kgateway data plane. The steps do not work with the agentgateway data plane.

    1. Create an HTTPRoute resource for the route that you want to modify. Note that the example selects the http Gateway that you created before you began.

      kubectl apply -f- <<EOF
      apiVersion: gateway.networking.k8s.io/v1
      kind: HTTPRoute
      metadata:
        name: httpbin-headers
        namespace: httpbin
      spec:
        parentRefs:
        - name: http
          namespace: kgateway-system
        hostnames:
          - headers.example
        rules:
          - backendRefs:
              - name: httpbin
                port: 8000
      EOF
    2. Create a TrafficPolicy that adds a my-response: hello header to a response. The following example attaches the TrafficPolicy to the http Gateway.

      kubectl apply -f- <<EOF
      apiVersion: gateway.kgateway.dev/v1alpha1
      kind: TrafficPolicy
      metadata:
        name: httpbin-headers
        namespace: kgateway-system
      spec:
        targetRefs:
        - group: gateway.networking.k8s.io
          kind: Gateway
          name: http
        headerModifiers:
          response:
            add:
            - name: my-response
              value: hello
      EOF

  2. Send a request to the httpbin app on the headers.example domain. Verify that you get back a 200 HTTP response code and that you see the my-response header in the response.

    curl -vi http://$INGRESS_GW_ADDRESS:8080/response-headers -H "host: headers.example:8080"
    curl -vi localhost:8080/response-headers -H "host: headers.example"

    Example output:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    
    * Mark bundle as not supporting multiuse
    < HTTP/1.1 200 OK
    HTTP/1.1 200 OK
    < access-control-allow-credentials: true
    access-control-allow-credentials: true
    < access-control-allow-origin: *
    access-control-allow-origin: *
    < content-type: application/json; encoding=utf-8
    content-type: application/json; encoding=utf-8
    < content-length: 3
    content-length: 3
    < x-envoy-upstream-service-time: 0
    x-envoy-upstream-service-time: 0
    < my-response: hello
    my-response: hello
    < server: envoy
    server: envoy
  3. Optional: Remove the resources that you created.

    kubectl delete httproute httpbin-headers -n httpbin
    kubectl delete httproute httpbin-headers -n httpbin
    kubectl delete TrafficPolicy httpbin-headers -n kgateway-system

Set response headers

Setting headers is similar to adding headers. If the response does not include the header, it is added by the ResponseHeaderModifier filter. However, if the request already contains the header, its value is overwritten with the value from the ResponseHeaderModifier filter.

  1. Set up a header modifier that sets a my-response: custom response header. Choose between the HTTPRoute for a Gateway API-native way, or TrafficPolicy for more flexible attachment options such as a gateway-level policy.

    kubectl apply -f- <<EOF
    apiVersion: gateway.networking.k8s.io/v1
    kind: HTTPRoute
    metadata:
      name: httpbin-headers
      namespace: httpbin
    spec:
      parentRefs:
      - name: http
        namespace: kgateway-system
      hostnames:
        - headers.example
      rules:
        - filters:
            - type: ResponseHeaderModifier
              responseHeaderModifier:
                set: 
                - name: my-response
                  value: custom
          backendRefs:
            - name: httpbin
              port: 8000
    EOF
    Setting Description
    spec.parentRefs The name and namespace of the gateway that serves this HTTPRoute. In this example, you use the http Gateway that was created as part of the get started guide.
    spec.rules.filters.type The type of filter that you want to apply to incoming requests. In this example, the ResponseHeaderModifier filter is used.
    spec.rules.filters.responseHeaderModifier.set The name and value of the response header that you want to set.
    spec.rules.backendRefs The backend destination you want to forward traffic to. In this example, all traffic is forwarded to the httpbin app that you set up as part of the get started guide.

    Note: The steps in this section use the Envoy-based kgateway data plane. The steps do not work with the agentgateway data plane.

    1. Create an HTTPRoute resource for the route that you want to modify. Note that the example selects the http Gateway that you created before you began.

      kubectl apply -f- <<EOF
      apiVersion: gateway.networking.k8s.io/v1
      kind: HTTPRoute
      metadata:
        name: httpbin-headers
        namespace: httpbin
      spec:
        parentRefs:
        - name: http
          namespace: kgateway-system
        hostnames:
          - headers.example
        rules:
          - backendRefs:
              - name: httpbin
                port: 8000
      EOF
    2. Create a TrafficPolicy that sets the my-response header to a custom value on a response. The following example attaches the TrafficPolicy to the http Gateway.

      kubectl apply -f- <<EOF
      apiVersion: gateway.kgateway.dev/v1alpha1
      kind: TrafficPolicy
      metadata:
        name: httpbin-headers
        namespace: kgateway-system
      spec:
        targetRefs:
        - group: gateway.networking.k8s.io
          kind: Gateway
          name: http
        headerModifiers:
          response:
            set:
            - name: my-response
              value: custom
      EOF

  2. Send a request to the httpbin app on the headers.example domain. Verify that you get back a 200 HTTP response code and that the my-response: custom header was set.

    curl -vi http://$INGRESS_GW_ADDRESS:8080/response-headers -H "host: headers.example:8080"
    curl -vi localhost:8080/response-headers -H "host: headers.example"

    Example output:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    
    ...
    * Request completely sent off
    < HTTP/1.1 200 OK
    HTTP/1.1 200 OK
    < access-control-allow-credentials: true
    access-control-allow-credentials: true
    < access-control-allow-origin: *
    access-control-allow-origin: *
    ...
    < my-response: custom
    my-response: custom
    < server: envoy
    server: envoy
  3. Optional: Remove the resources that you created.

    kubectl delete httproute httpbin-headers -n httpbin
    kubectl delete httproute httpbin-headers -n httpbin
    kubectl delete TrafficPolicy httpbin-headers -n kgateway-system

Remove response headers

You can remove HTTP headers from a response before the response is sent back to the client.

  1. Send a request to the httpbin app and find the content-length header.

    curl -vi http://$INGRESS_GW_ADDRESS:8080/response-headers -H "host: www.example.com:8080"
    curl -vi localhost:8080/response-headers -H "host: www.example.com"

    Example output:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    
    ...
    * Mark bundle as not supporting multiuse
    < HTTP/1.1 200 OK
    HTTP/1.1 200 OK
    < access-control-allow-credentials: true
    access-control-allow-credentials: true
    < access-control-allow-origin: *
    access-control-allow-origin: *
    < content-type: application/json; encoding=utf-8
    content-type: application/json; encoding=utf-8
    < content-length: 3
    content-length: 3
    < x-envoy-upstream-service-time: 0
    x-envoy-upstream-service-time: 0
    < server: envoy
    server: envoy
  2. Set up a header modifier that removes the content-length header from the response. Choose between the HTTPRoute for a Gateway API-native way, or TrafficPolicy for more flexible attachment options such as a gateway-level policy.

    kubectl apply -f- <<EOF
    apiVersion: gateway.networking.k8s.io/v1
    kind: HTTPRoute
    metadata:
      name: httpbin-headers
      namespace: httpbin
    spec:
      parentRefs:
      - name: http
        namespace: kgateway-system
      hostnames:
        - headers.example
      rules:
        - filters:
            - type: ResponseHeaderModifier
              responseHeaderModifier:
                remove: 
                - content-length
          backendRefs:
            - name: httpbin
              port: 8000
    EOF
    Setting Description
    spec.parentRefs The name and namespace of the gateway that serves this HTTPRoute. In this example, you use the http gateway that was created as part of the get started guide.
    spec.rules.filters.type The type of filter that you want to apply. In this example, the ResponseHeaderModifier filter is used.
    spec.rules.filters.responseHeaderModifier.remove The name of the response header that you want to remove.
    spec.rules.backendRefs The backend destination you want to forward traffic to. In this example, all traffic is forwarded to the httpbin app that you set up as part of the get started guide.
    1. Create an HTTPRoute resource for the route that you want to modify. Note that the example selects the http Gateway that you created before you began.

      kubectl apply -f- <<EOF
      apiVersion: gateway.networking.k8s.io/v1
      kind: HTTPRoute
      metadata:
        name: httpbin-headers
        namespace: httpbin
      spec:
        parentRefs:
        - name: http
          namespace: kgateway-system
        hostnames:
          - headers.example
        rules:
          - backendRefs:
              - name: httpbin
                port: 8000
      EOF
    2. Create a TrafficPolicy that removes the content-length header from a response. The following example attaches the TrafficPolicy to the http Gateway.

      kubectl apply -f- <<EOF
      apiVersion: gateway.kgateway.dev/v1alpha1
      kind: TrafficPolicy
      metadata:
        name: httpbin-headers
        namespace: kgateway-system
      spec:
        targetRefs:
        - group: gateway.networking.k8s.io
          kind: Gateway
          name: http
        headerModifiers:
          response:
            remove:
            - content-length
      EOF

  3. Send a request to the httpbin app on the headers.example domain . Verify that the content-length response header is removed.

    curl -vi http://$INGRESS_GW_ADDRESS:8080/response-headers -H "host: headers.example:8080"
    curl -vi localhost:8080/response-headers -H "host: headers.example"

    Example output:

    * Mark bundle as not supporting multiuse
    < HTTP/1.1 200 OK
    HTTP/1.1 200 OK
    < access-control-allow-credentials: true
    access-control-allow-credentials: true
    < access-control-allow-origin: *
    access-control-allow-origin: *
    < content-type: application/json; encoding=utf-8
    content-type: application/json; encoding=utf-8
    < x-envoy-upstream-service-time: 0
    x-envoy-upstream-service-time: 0
    < server: envoy
    server: envoy
    < transfer-encoding: chunked
    transfer-encoding: chunked
  4. Optional: Remove the resources that you created.

    kubectl delete httproute httpbin-headers -n httpbin
    kubectl delete httproute httpbin-headers -n httpbin
    kubectl delete TrafficPolicy httpbin-headers -n kgateway-system

Source a header value from a Secret

If a response header value is sensitive, such as an audit signing key or a service-to-service token destined for a trusted client, you might not want to commit it to a manifest in plain text. You can source a response header value from a Kubernetes Secret by replacing value with secretRef on a set or add entry in a TrafficPolicy. kgateway resolves the Secret at translation time, so the value never appears in the policy spec. If the Secret changes later, kgateway re-translates the affected policies automatically.

ℹ️
This option is available only on the TrafficPolicy. The Gateway API HTTPRoute ResponseHeaderModifier filter does not support secretRef.

The same defaulting rules and cross-namespace ReferenceGrant requirement that apply to request headers from a Secret also apply here. The following example shows the basic flow for responses.

  1. Create a Secret that holds the value you want to inject. The data keys do not need to match the eventual header names.

    kubectl apply -f- <<EOF
    apiVersion: v1
    kind: Secret
    metadata:
      name: response-signing
      namespace: kgateway-system
    type: Opaque
    stringData:
      signing-key: my-response-signing-key
    EOF
  2. Create an HTTPRoute for the httpbin app. The example selects the http Gateway that you created before you began.

    kubectl apply -f- <<EOF
    apiVersion: gateway.networking.k8s.io/v1
    kind: HTTPRoute
    metadata:
      name: httpbin-headers
      namespace: httpbin
    spec:
      parentRefs:
      - name: http
        namespace: kgateway-system
      hostnames:
        - headers.example
      rules:
        - backendRefs:
            - name: httpbin
              port: 8000
    EOF
  3. Create a TrafficPolicy that sets a response header from the response-signing Secret. The following example attaches the TrafficPolicy to the http Gateway.

    kubectl apply -f- <<EOF
    apiVersion: gateway.kgateway.dev/v1alpha1
    kind: TrafficPolicy
    metadata:
      name: httpbin-secret-response-headers
      namespace: kgateway-system
    spec:
      targetRefs:
      - group: gateway.networking.k8s.io
        kind: Gateway
        name: http
      headerModifiers:
        response:
          set:
          - name: X-Response-Signature
            secretRef:
              name: response-signing
              key: signing-key
    EOF

    The TrafficPolicy targetRefs field does not accept a namespace. The policy must live in the same namespace as the resource it targets. To scope this example to a single HTTPRoute instead of the whole Gateway, change kind to HTTPRoute and name to the route’s name, and create the TrafficPolicy (and the Secret) in the route’s namespace.

    Setting Description
    headerModifiers.response.set.name The HTTP header name that the client receives.
    headerModifiers.response.set.secretRef.name The name of the Kubernetes Secret to read the value from. If the Secret does not exist when the policy is applied, the policy reports Accepted=False and the affected route returns a 500 response.
    headerModifiers.response.set.secretRef.key The key in the Secret’s data to use as the header value. Optional. If key is omitted, it defaults to the value of headerModifiers.response.set.name.
    headerModifiers.response.set.secretRef.namespace The namespace of the Secret. Optional. If namespace is omitted, it defaults to the namespace of the TrafficPolicy. Cross-namespace references require a ReferenceGrant.
  4. Send a request to the httpbin app on the headers.example domain and confirm that the response includes the X-Response-Signature header with the value from the Secret.

curl -vi http://$INGRESS_GW_ADDRESS:8080/response-headers -H "host: headers.example:8080"
curl -vi localhost:8080/response-headers -H "host: headers.example"

Example output:

HTTP/1.1 200 OK
access-control-allow-credentials: true
access-control-allow-origin: *
content-type: application/json; encoding=utf-8
x-envoy-upstream-service-time: 0
x-response-signature: my-response-signing-key
server: envoy
  1. Optional: When you are finished, clean up the resources that you created.
kubectl delete httproute httpbin-headers -n httpbin
kubectl delete TrafficPolicy httpbin-secret-response-headers -n kgateway-system
kubectl delete secret response-signing -n kgateway-system

Field defaulting

The name field on a set or add entry and the key field on secretRef are both optional. How kgateway resolves a header value depends on which combination of fields you provide. If the Secret does not contain the key or name data, the policy reports Accepted=False and the affected route returns a 500 response.

name and secretRef.key both set

kgateway sets the name header to the value of the secretRef.key data key in the Secret.

headerModifiers:
  response:
    set:
    - name: X-Response-Signature
      secretRef:
        name: response-signing
        key: signing-key

secretRef.key omitted

kgateway sets the name header to the value of the Secret data key that matches name.

headerModifiers:
  response:
    set:
    - name: X-Response-Signature
      secretRef:
        name: response-signing

name omitted

kgateway sets a header named after secretRef.key to the value of that data key in the Secret.

headerModifiers:
  response:
    set:
    - secretRef:
        name: response-signing
        key: signing-key

Both name and secretRef.key omitted

kgateway injects every entry in the Secret as a response header. Each data key becomes a header name. Use this combination to mirror an entire Secret into headers without listing each entry individually.

headerModifiers:
  response:
    set:
    - secretRef:
        name: response-signing

Dynamic response headers

You can return dynamic information about the response in the response header. For more information, see the Envoy docs for Custom request/response headers.

Keep in mind that some variables are available only at certain times. For example, response codes (%RESPONSE_CODE%) are only available after the response has been sent to the client. If you set a response code in a request header, the value is empty.

You might use some of the following common values in your request or response headers.

Request and response information:

  • %REQ(:METHOD)% - HTTP method
  • %REQ(:PATH)% - Request path
  • %REQ(:AUTHORITY)% - Host header
  • %REQ(HEADER_NAME)% - Any request header
  • %RESP(HEADER_NAME)% - Any response header
  • %RESPONSE_CODE% - HTTP response code
  • %RESPONSE_FLAGS% - Response flags

Connection information:

  • %DOWNSTREAM_REMOTE_ADDRESS% - Client IP address with port
  • %DOWNSTREAM_REMOTE_ADDRESS_WITHOUT_PORT% - Client IP address without port
  • %DOWNSTREAM_LOCAL_ADDRESS% - Local address
  • %DOWNSTREAM_CONNECTION_ID% - Connection ID

Timing information:

  • %START_TIME% - Request start time
  • %DURATION% - Request duration

Upstream information:

  • %UPSTREAM_HOST% - Upstream host
  • %UPSTREAM_CLUSTER% - Upstream Envoy cluster
  • %UPSTREAM_LOCAL_ADDRESS% - Upstream local address

Data transfer:

  • %BYTES_RECEIVED% - Bytes received
  • %BYTES_SENT% - Bytes sent

For more potential values, see Command operators in the Envoy docs.

The steps in this section use the Envoy-based kgateway data plane. The steps do not work with the agentgateway data plane.
  1. Set up a header modifier that sets the X-Response-Code header with the value of the HTTP response code. Choose between the HTTPRoute for a Gateway API-native way, or TrafficPolicy for more flexible attachment options such as a gateway-level policy.

    kubectl apply -f- <<EOF
    apiVersion: gateway.networking.k8s.io/v1
    kind: HTTPRoute
    metadata:
      name: httpbin-headers
      namespace: httpbin
    spec:
      parentRefs:
      - name: http
        namespace: kgateway-system
      hostnames:
        - headers.example
      rules:
        - filters:
            - type: ResponseHeaderModifier
              responseHeaderModifier:
                set: 
                  - name: x-response-code
                    value: "%RESPONSE_CODE%"
          backendRefs:
            - name: httpbin
              port: 8000
    EOF
    Setting Description
    spec.parentRefs The name and namespace of the gateway that serves this HTTPRoute. In this example, you use the http Gateway that was created as part of the get started guide.
    spec.rules.filters.type The type of filter that you want to apply to responses. In this example, the ResponseHeaderModifier filter is used.
    spec.rules.filters.responseHeaderModifier.set The response header that you want to set. In this example, the x-response-code header is set to the HTTP response code. For more potential values, see Command operators in the Envoy docs.
    spec.rules.backendRefs The backend destination you want to forward traffic to. In this example, all traffic is forwarded to the httpbin app that you set up as part of the get started guide.
    1. Create an HTTPRoute resource for the route that you want to modify. Note that the example selects the http Gateway that you created before you began.

      kubectl apply -f- <<EOF
      apiVersion: gateway.networking.k8s.io/v1
      kind: HTTPRoute
      metadata:
        name: httpbin-headers
        namespace: httpbin
      spec:
        parentRefs:
        - name: http
          namespace: kgateway-system
        hostnames:
          - headers.example
        rules:
          - backendRefs:
              - name: httpbin
                port: 8000
      EOF
    2. Create a TrafficPolicy that sets the x-response-code header to the HTTP response code. For more potential values, see Command operators in the Envoy docs. The following example attaches the TrafficPolicy to the http Gateway.

      kubectl apply -f- <<EOF
      apiVersion: gateway.kgateway.dev/v1alpha1
      kind: TrafficPolicy
      metadata:
        name: httpbin-headers
        namespace: kgateway-system
      spec:
        targetRefs:
        - group: gateway.networking.k8s.io
          kind: Gateway
          name: http
        headerModifiers:
          response:
            set:
            - name: x-response-code
              value: "%RESPONSE_CODE%"
      EOF

  2. Send a request to the httpbin app on the headers.example domain. Verify that the x-response-code response header is set to the HTTP response code.

    curl -vi http://$INGRESS_GW_ADDRESS:8080/response-headers -H "host: headers.example:8080"
    curl -vi localhost:8080/response-headers -H "host: headers.example"

    Example output:

    HTTP/1.1 200 OK
    access-control-allow-credentials: true
    access-control-allow-origin: *
    content-type: application/json; encoding=utf-8
    date: Tue, 23 Sep 2025 20:05:29 GMT
    content-length: 479
    x-envoy-upstream-service-time: 0
    x-response-code: 200
    server: envoy
  3. Optional: Clean up the resources that you created.

    kubectl delete httproute httpbin-headers -n httpbin
    kubectl delete httproute httpbin-headers -n httpbin
    kubectl delete TrafficPolicy httpbin-headers -n kgateway-system