Envoy, when used as a transparent proxy for gRPC, can dynamically rewrite request headers based on the gRPC method being called.

Let’s see Envoy in action. Imagine a client making a gRPC call to a service. Instead of directly connecting to the service, the client’s traffic is intercepted and routed to Envoy. Envoy inspects the gRPC request, specifically the :path header which contains the service and method name (e.g., /com.example.Greeter/SayHello). Based on this, Envoy can then apply specific routing rules or transformations before forwarding the request to the appropriate backend service.

Here’s a simplified Envoy configuration demonstrating this:

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
          codec_type: AUTO
          route_config:
            virtual_hosts:
            - name: local_service
              routes:
              - match:
                  grpc: {} # Match any gRPC request
                route:
                  cluster: grpc_service_cluster
              - match:
                  prefix: "/"
                route:
                  cluster: default_service_cluster
          http_filters:
          - name: envoy.filters.http.router
            typed_config:
              "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
  clusters:
  - name: grpc_service_cluster
    connect_timeout: 0.25s
    type: LOGICAL_DNS
    lb_policy: ROUND_ROBIN
    # For transparent proxying, we often use a specific cluster
    # that resolves to the actual backend service.
    # In a real scenario, this would be a DNS name or IP.
    dns_lookup_family: V4_ONLY
    load_assignment:
      cluster_name: grpc_service_cluster
      endpoints:
      - lb_endpoints:
        - endpoint:
            address:
              socket_address:
                address: grpc.backend.svc.cluster.local
                port_value: 50051
  - name: default_service_cluster
    connect_timeout: 0.25s
    type: LOGICAL_DNS
    lb_policy: ROUND_ROBIN
    dns_lookup_family: V4_ONLY
    load_assignment:
      cluster_name: default_service_cluster
      endpoints:
      - lb_endpoints:
        - endpoint:
            address:
              socket_address:
                address: default.backend.svc.cluster.local
                port_value: 80

The core problem Envoy solves here is decoupling the client’s view of service endpoints from the actual network topology. Transparent proxying means that the client doesn’t need to be aware that Envoy is intercepting its traffic. This is typically achieved using iptables rules on Linux to redirect traffic destined for a specific port (or all traffic) to Envoy’s listening port.

Internally, Envoy uses a combination of network filters. The HttpConnectionManager is key. When it receives traffic, it first checks if it’s HTTP/2 (which gRPC uses). If it is, it then inspects the gRPC-specific headers. The :path header is crucial, as it contains the fully qualified method name. Envoy’s routing engine uses this to match against route_config. In the example above, match: grpc: {} is a special matcher that specifically targets gRPC requests, allowing for distinct routing logic. If the request isn’t matched as gRPC, it falls through to the general prefix matching.

The cluster configuration defines where Envoy forwards the traffic. For gRPC, type: LOGICAL_DNS is common, allowing Envoy to resolve service names dynamically. The grpc_service_cluster would point to your actual gRPC backend.

A subtle but powerful aspect of Envoy’s gRPC handling is its ability to transform requests. While not explicitly shown in this basic example, you can use filters like envoy.filters.http.grpc_field_extractor or envoy.filters.http.header_to_grpc_metadata to manipulate request metadata or add custom headers based on the gRPC method or even the request payload. This allows for sophisticated traffic management, such as injecting authentication tokens or enriching requests with contextual information before they reach the backend service.

When configuring transparent proxying with Envoy, it’s common to use iptables on Linux. A typical setup involves redirecting incoming TCP traffic on a specific port (e.g., 50051 for gRPC) to Envoy’s listener port (e.g., 8080). This is done with iptables rules that use the REDIRECT target. For example:

# Redirect traffic from port 50051 to Envoy's port 8080
sudo iptables -t nat -A PREROUTING -p tcp --dport 50051 -j REDIRECT --to-port 8080

This rule ensures that any TCP packets arriving at port 50051 are transparently sent to Envoy on port 8080, without the client needing to know about Envoy.

The next challenge you’ll likely encounter is implementing advanced gRPC features like call-aware load balancing or retries, which Envoy can manage effectively.

Want structured learning?

Take the full Envoy course →