Envoy’s HTTP Connection Manager doesn’t route traffic in the traditional sense; it directs it based on rules you define.

Let’s watch it in action. Imagine we have two backend services: service-a on port 8080 and service-b on 8081. We want Envoy to send requests to /a/* to service-a and requests to /b/* to service-b.

Here’s a snippet of an Envoy configuration that achieves this:

static_resources:
  listeners:
  - name: listener_0
    address:
      socket_address:
        address: 0.0.0.0
        port_value: 10000
    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: "/a/"
                route:
                  cluster: service_a
              - match:
                  prefix: "/b/"
                route:
                  cluster: service_b
          http_filters:
          - name: envoy.filters.http.router
            typed_config: {}
  clusters:
  - name: service_a
    connect_timeout: 0.25s
    type: LOGICAL_DNS
    lb_policy: ROUND_ROBIN
    dns_lookup_family: V4_ONLY
    load_assignment:
      cluster_name: service_a
      endpoints:
      - lb_endpoints:
        - endpoint:
            address:
              socket_address:
                address: 127.0.0.1
                port_value: 8080
  - name: service_b
    connect_timeout: 0.25s
    type: LOGICAL_DNS
    lb_policy: ROUND_ROBIN
    dns_lookup_family: V4_ONLY
    load_assignment:
      cluster_name: service_b
      endpoints:
      - lb_endpoints:
        - endpoint:
            address:
              socket_address:
                address: 127.0.0.1
                port_value: 8081

When a request arrives at port 10000, the http_connection_manager filter intercepts it. It then consults its route_config. Within route_config, it looks at virtual_hosts. Here, we have one virtual host that matches any incoming domain (domains: ["*"]). Inside this virtual host, it checks the routes.

The first route has a match of prefix: "/a/". If the request URI starts with /a/, Envoy will route it to the cluster named service_a. If it doesn’t match the first route, it checks the next one. The second route has a match of prefix: "/b/". If the request URI starts with /b/, it’s routed to the cluster named service_b.

The clusters section defines where Envoy can send traffic. service_a is configured to send traffic to 127.0.0.1:8080, and service_b to 127.0.0.1:8081. The http_filters list ensures that the router filter is enabled, which is the filter responsible for actually performing the upstream connection and forwarding the request based on the routing decisions made by the http_connection_manager.

The core of routing in Envoy’s HTTP Connection Manager lies in the route_config, specifically the virtual_hosts and routes sections. Each route can have various matchers, not just prefixes. You can match by exact path, regular expressions, or even HTTP headers. This flexibility allows for sophisticated traffic splitting, A/B testing, and canary deployments by defining multiple routes within a virtual host, often with different weights or conditions. The route also specifies the cluster to send the traffic to, which is a logical group of upstream endpoints.

One critical aspect often overlooked is the order of routes within a virtual_host. Envoy evaluates routes sequentially from top to bottom. The first route that matches the incoming request is used, and no further routes are considered. This means more specific routes should generally be placed before more general ones to ensure they are matched correctly. For example, a route matching an exact path like /a/specific should come before a route matching the prefix /a/.

The journey doesn’t end with routing; the router filter is what executes the actual upstream request. It takes the cluster name determined by the http_connection_manager and initiates a connection to one of the endpoints within that cluster, respecting load balancing policies.

The next logical step after mastering basic routing is to explore advanced routing features like weighted routing for traffic shifting and header-based routing for fine-grained control.

Want structured learning?

Take the full Envoy course →