Envoy’s virtual hosts and routes are how it decides where to send incoming requests, but they aren’t just simple mappings; they’re a powerful, ordered decision tree that can inspect and modify requests as they flow through.

Let’s see this in action. Imagine a request coming into Envoy for api.example.com/v1/users. Envoy first looks at the Host header to pick the right virtual_host. If it finds api.example.com, it then dives into that virtual host’s routes. It checks each route sequentially until one matches. A match isn’t just on the path, but can include headers, query parameters, and even the request method.

Here’s a simplified Envoy configuration snippet:

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
          route_config:
            virtual_hosts:
            - name: api_service
              domains: ["api.example.com"]
              routes:
              - match:
                  prefix: "/v1/users"
                  headers:
                  - name: "x-api-version"
                    exact_match: "1.0"
                route:
                  cluster: users_v1_cluster
              - match:
                  prefix: "/v1/users"
                route:
                  cluster: users_v1_fallback_cluster
              - match:
                  prefix: "/v1/products"
                route:
                  cluster: products_cluster
            http_filters:
            - name: envoy.filters.http.router
              typed_config:
                "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router

In this example, a request to api.example.com/v1/users with the header x-api-version: 1.0 will hit the users_v1_cluster. If the x-api-version header is missing or different, but the path is still /v1/users, it will fall through to the users_v1_fallback_cluster. A request to /v1/products would go to products_cluster.

The core problem this solves is routing traffic based on rich request inspection. Instead of just a single ingress point, Envoy can act as a sophisticated API gateway or service mesh proxy, intelligently directing requests to the correct backend service based on a multitude of criteria.

Internally, when a request arrives at the HttpConnectionManager, it first identifies the appropriate virtual_host by matching the request’s Host header against the domains list. Once a virtual_host is selected, Envoy iterates through its routes array in order. The first route whose match criteria are met by the incoming request is chosen. This ordered matching is critical. If multiple routes could potentially match, only the first one in the list is used.

The match object is where the magic happens. It can specify:

  • prefix: Matches the beginning of the request path.
  • path: Matches the exact request path.
  • safe_regex: Matches the path using a regular expression.
  • headers: A list of headers that must be present and match specific values.
  • query_parameters: Similar to headers, for matching query string parameters.
  • methods: Matches the HTTP request method (GET, POST, etc.).

If a route is matched, Envoy then performs the action defined in the route object. The most common action is cluster, which tells Envoy to forward the request to a named upstream cluster. Other actions include route_action for more complex routing logic, redirect for HTTP redirects, and direct_response for returning a static response.

The route_action within a route is where you can further modify the request before sending it. You can change the cluster it’s routed to, add or modify request headers (request_headers_to_add), rewrite the path (prefix_rewrite, path_redirect), and more. This allows for sophisticated transformations as requests pass through Envoy.

A common, yet often overlooked, detail is how prefix matching interacts with ordered routes. If you have a route for /api and another for /api/v1, and a request comes in for /api/v1/users, the /api prefix match will happen first. If your routes aren’t ordered carefully with more specific prefixes first, you can easily send traffic to the wrong place. Always place more specific path matches (like /api/v1) before less specific ones (like /api) within the same virtual_host.

After the HttpConnectionManager has determined the route and potentially modified the request, it passes the request to the next filter in the chain. Typically, this is the envoy.filters.http.router filter, which takes over and actually sends the request to the chosen upstream cluster.

The next challenge you’ll face is handling different versions of an API using query parameters or more complex header matching.

Want structured learning?

Take the full Envoy course →