Envoy’s core strength isn’t just routing traffic; it’s about arbitrarily reconfiguring its entire understanding of the network topology, including what services exist, where they are, and how to talk to them, all without a single restart.

Let’s see this in action. Imagine we have a simple frontend service that needs to talk to a backend service. We’ll configure Envoy to act as a proxy for frontend, so frontend thinks it’s talking directly to backend, but Envoy is intercepting and forwarding.

Here’s a complete static configuration:

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:
            name: local_route
            virtual_hosts:
            - name: local_service
              domains: ["*"]
              routes:
              - match:
                  prefix: "/api/v1/users"
                route:
                  cluster: users_service
          http_filters:
          - name: envoy.filters.http.router
            typed_config:
              "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
  clusters:
  - name: users_service
    connect_timeout: 0.25s
    type: LOGICAL_DNS
    lb_policy: ROUND_ROBIN
    load_assignment:
      cluster_name: users_service
      endpoints:
      - lb_endpoints:
        - endpoint:
            address:
              socket_address:
                address: 192.168.1.100
                port_value: 80
  - name: another_service
    connect_timeout: 1s
    type: STATIC
    load_assignment:
      cluster_name: another_service
      endpoints:
      - lb_endpoints:
        - endpoint:
            address:
              socket_address:
                address: 192.168.1.101
                port_value: 9090
        - endpoint:
            address:
              socket_address:
                address: 192.168.1.102
                port_value: 9090
admin:
  access_log_path: /tmp/envoy.log
  address:
    socket_address:
      address: 127.0.0.1
      port_value: 9901

In this config, we’ve set up two main parts: listeners and clusters.

The listener section defines listener_0. It’s listening on all interfaces (0.0.0.0) on port 8080. When a request comes in, the http_connection_manager filter is invoked. This filter is the brain for L7 routing. It looks at the request’s Host header and path. Here, any request to * (meaning any host) with a path starting with /api/v1/users will be routed to a cluster named users_service. The stat_prefix is used for Prometheus metrics.

The clusters section defines the destinations. We have users_service. Its type is LOGICAL_DNS. This means Envoy will resolve users_service via DNS (if it were a real DNS name) or, in this static config, it’s effectively hardcoded to resolve to 192.168.1.100:80. Envoy will then load balance across its endpoints using ROUND_ROBIN. We also define another_service with type: STATIC, which means its endpoints are explicitly listed: 192.168.1.101:9090 and 192.168.1.102:9090.

The admin section is crucial for observability and control. It gives you a web interface (on 127.0.0.1:9901 in this case) to inspect Envoy’s configuration, statistics, and even dynamically update it (though this config is static).

When frontend sends a request like GET /api/v1/users/123 to localhost:8080 (where Envoy is listening), Envoy’s http_connection_manager sees the /api/v1/users prefix, consults its route_config, and determines that this request should go to the users_service cluster. It then selects an endpoint from users_service (in this case, 192.168.1.100:80) and forwards the request. The users_service receives the request as if it came directly from Envoy, not from frontend.

The connect_timeout values are critical for how quickly Envoy abandons a connection attempt to an upstream host. A lower value, like 0.25s for users_service, means Envoy is aggressive about failing fast if the backend is unresponsive. A higher value, like 1s for another_service, gives backends more time to respond.

What most people don’t realize is that Envoy’s type: LOGICAL_DNS doesn’t just do DNS lookups. When you provide an address in the load_assignment for a LOGICAL_DNS cluster, Envoy will treat that address as the sole resolved endpoint for that logical DNS name. It’s a way to bootstrap a DNS-like name in a static configuration without relying on an actual DNS server for that specific cluster.

After successfully routing traffic, you’ll likely want to explore Envoy’s load balancing policies beyond ROUND_ROBIN, such as LEAST_REQUEST or RING_HASH.

Want structured learning?

Take the full Envoy course →