Envoy’s route configuration doesn’t just match requests; it actively transforms them, and headers are its primary canvas.

Let’s see how Envoy can manipulate request headers using a simple HTTP filter. Imagine we have an upstream service that expects a specific X-User-Id header, but our incoming requests don’t always provide it. We can use Envoy to add it.

static_resources:
  listeners:
  - name: listener_0
    address:
      socket_address:
        address: 0.0.0.0
        port_value: 8080
    filter_chains:
    - filters:
      - name: envoy.filters.network.http_connection_manager
        typed_config:
          "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
          stat_prefix: ingress_http
          route_config:
            name: local_route
            virtual_hosts:
            - name: local_service
              domains: ["*"]
              routes:
              - match:
                  prefix: "/"
                route:
                  cluster: some_service
                  # Add a header
                  request_headers_to_add:
                  - header:
                      key: "X-User-Id"
                      value: "default-user-123"
                  # Remove a header
                  request_headers_to_remove:
                  - "X-Forwarded-Client-Cert"
                  # Rewrite a header
                  request_headers_to_manipulate:
                  - header:
                      key: "Host"
                    # This uses string interpolation with the original value
                    # If the original Host was "example.com", it becomes "rewritten-example.com"
                    value_substitution: "rewritten-%s"
                    # This is the key for manipulation, not a direct value
                    # The actual manipulation happens based on the 'value_substitution' field
                    operation:
                      op: "POST_REPLACE" # POST_REPLACE is used for substitution
          http_filters:
          - name: envoy.filters.http.router
            typed_config:
              "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
  clusters:
  - name: some_service
    connect_timeout: 0.25s
    type: LOGICAL_DNS
    # This is a placeholder; replace with your actual upstream service
    lb_policy: ROUND_ROBIN
    # Example DNS for a local service
    dns_lookup_family: V4_ONLY
    load_assignment:
      cluster_name: some_service
      endpoints:
      - lb_endpoints:
        - endpoint:
            address:
              socket_address:
                address: 127.0.0.1
                port_value: 9000

When a request hits Envoy on port 8080, it first enters the http_connection_manager. The route_config then applies transformations. In this example:

  • Adding Headers: The request_headers_to_add section injects a new header named X-User-Id with the value default-user-123 into every request that matches this route. This is useful for adding required metadata that might be missing from the client.
  • Removing Headers: request_headers_to_remove eliminates the X-Forwarded-Client-Cert header. This is often done for security or to prevent upstream services from seeing potentially sensitive client certificate information that Envoy has already processed.
  • Rewriting Headers: The request_headers_to_manipulate section allows for more complex modifications. Here, we’re targeting the Host header. The value_substitution: "rewritten-%s" tells Envoy to take the original value of the Host header and prepend rewritten- to it. The %s is a placeholder for the original value. The operation: { op: "POST_REPLACE" } specifies that this substitution should occur after the header is potentially added or if it already exists. This is powerful for dynamically altering destination information or injecting context.

The core problem this solves is achieving granular control over request metadata as it flows through your network. Envoy acts as an intelligent proxy, able to enrich, sanitize, or modify headers based on complex routing rules, without requiring changes to your application code. This separation of concerns simplifies microservice communication and allows for centralized policy enforcement.

The value_substitution field supports string interpolation using %s for the original header value, and also allows for referencing other headers using the syntax %{HEADER_NAME}. For instance, you could rewrite a User-Agent header to include the original User-Agent and a specific internal identifier: value_substitution: "%{User-Agent} (internal-proxy)". This allows for dynamic construction of header values based on existing request context.

The next step in header manipulation is often implementing more advanced transformations using Lua or WebAssembly filters for logic that goes beyond simple additions, removals, or substitutions.

Want structured learning?

Take the full Envoy course →