The HTTP Connection Manager and HTTP Filter Chains
How Envoy turns raw bytes into HTTP requests it can act on, and the ways you can plug your own logic into that pipeline.
Most of what people configure day to day in Envoy, header manipulation, access logging, retries, external authorization, happens inside one particular network filter: the HTTP Connection Manager. This guide covers what it does, how its own filter chain is structured, and the ways you can extend it with logic that isn’t built in.
HCM: from bytes to HTTP
A listener’s filter chain starts at the network level, operating on a raw stream of bytes, with no concept yet of “a request.” The HTTP Connection Manager (HCM) is the network filter that changes that: it parses the byte stream into HTTP requests, headers, bodies, and trailers, and from that point on, everything downstream of it in the chain can work with HTTP semantics rather than raw bytes.
This is also where request-level features live: access logging, request and response manipulation, and, crucially, the HCM’s own nested chain of HTTP filters, which is a separate concept from the network-level filter chain it sits inside.
The HTTP filter chain
Inside the HCM, HTTP filters run in a defined order against each HTTP request: CORS handling, rate limiting, external authorization (a call out to another service to decide whether a request is allowed), Lua scripting, WebAssembly (Wasm) modules, and more. The chain’s last filter is typically a terminal filter, almost always the router filter, whose job is to hand the request off to route matching and, from there, to a cluster.
Order matters here. A rate-limiting filter placed before an authentication filter behaves differently than one placed after it, and Envoy’s configuration for a filter chain is explicit about where each filter sits relative to the others, not just which filters are present.
Extending Envoy: your options
When none of Envoy’s built-in filters do what you need, there are several ways to add custom logic, each with a different cost and a different reach:
- Native C++ filters, compiled directly into a custom Envoy build. This is the most capable option and the most involved: it means maintaining your own Envoy build rather than consuming an upstream one. Istio’s own Envoy distribution takes this route for some of its built-in behavior.
- External processing (ext_proc), where Envoy calls out to a separate service mid-request, that service does whatever processing it needs, and returns control back to the filter chain. This keeps custom logic out of Envoy’s own process entirely, at the cost of a network hop per request.
- Lua scripting, where a Lua filter runs an embedded script against the request and/or response. Quick to write and iterate on, without a rebuild or a separate service, at the cost of being limited to what Lua and Envoy’s Lua API expose.
- WebAssembly (Wasm), where you compile a filter to a
.wasmmodule in a language of your choice, and Envoy loads and executes it as part of the chain. This is the extension mechanism most oriented toward distributing and reusing a filter as a portable artifact, rather than a script or a custom build.
Which one fits depends on how much control you need versus how much operational overhead you’re willing to take on: a Lua script is usually the fastest way to try something, a Wasm module is the more portable way to ship it once you know it works, and a custom C++ build or an external service is what’s left once neither of those can do what you need.
What a filter actually needs to be configured
Whichever extension mechanism you use, configuring an HTTP filter always comes down to three things:
- The filter’s name, a stable identifier.
- Its type URL, which tells Envoy’s configuration system exactly what kind of filter this is and what shape its configuration takes.
- The filter-specific configuration itself, the fields that particular filter actually accepts.
Envoy’s own documentation page for each filter lists its type URL and configuration fields, though the documentation for a given filter is often spread across several linked reference pages rather than in one place; finding the exact syntax for a specific field frequently means following a chain of links rather than reading top to bottom. Searching public code hosts for a filter’s type URL is a practical shortcut once you know it: for anything reasonably common, someone has usually already published a working configuration you can start from rather than assembling one from reference docs alone.
Where this shows up if you’re using a service mesh
If Envoy is running as a sidecar under Istio or a similar mesh, you’re rarely writing raw HTTP filter configuration by hand for common cases, the mesh’s own APIs (routing rules, security policies) generate it for you. But for anything the mesh’s high-level APIs don’t cover, and there’s always something they don’t cover, you end up configuring an HTTP filter directly through whatever lower-level escape hatch the mesh provides. The next guide in this series covers exactly that, using Istio’s EnvoyFilter resource as the example.
Popular Guides
-
Envoy Architecture: Listeners, Clusters, and Filter Chains How a request actually moves through Envoy, from the socket it arrives on to the upstream it's sent to. -
Where Envoy Fits: Edge, Front Proxy, and Service Mesh Patterns The same proxy, deployed three different ways — and why 'lightweight enough to run per instance' is what makes all three possible.