Understanding Envoy Proxy
A technical walkthrough of Envoy Proxy — what it is, why it exists alongside Nginx and HAProxy, how xDS makes it dynamic, and how it powers the Kubernetes Gateway API.
What is Envoy Proxy?
Envoy is an open-source L3/L4/L7 proxy, originally built at Lyft and now a graduated CNCF project. It's written in C++ for performance, and it sits in the network path of your services to handle routing, load balancing, retries, TLS termination, and observability — so application code doesn't have to.
At its core, Envoy operates as an L3/L4 network proxy: it moves bytes and manages connections at the IP and TCP/UDP level without needing to understand what's inside them. On top of that foundation, Envoy layers in L7 filters — protocol-aware modules that understand HTTP, gRPC, and other application protocols, enabling behavior like path-based routing, retries on failed requests, and header manipulation.
Why Envoy Exists
Reverse proxies like Nginx and HAProxy have existed for years and handle static, slowly-changing infrastructure extremely well. Envoy wasn't built to replace them everywhere — it was built to solve a problem they weren't designed for: environments where backends change constantly, like Kubernetes clusters where pods scale up and down by the minute.
| Nginx / HAProxy | Envoy | |
|---|---|---|
| Config updates | Regenerate config file, reload process | Pushed live via API (xDS), zero downtime |
| Primary traffic pattern | North-south (internet → app) | Both north-south and east-west (service-to-service) |
| Observability | Basic, often needs extra modules | Deep metrics, tracing, and logging built in |
| gRPC / HTTP2 support | Partial | First-class |
| Service mesh foundation | Not designed for this | The default data plane for Istio and others |
The honest takeaway: if your architecture is a handful of stable services with mostly inbound traffic, Nginx or HAProxy is simpler to run and completely reasonable. Envoy earns its added operational complexity when you're dealing with high pod churn, need service-to-service resilience (retries, circuit breaking, mTLS), or want to build a service mesh.
A common point of confusion: even Nginx-based Kubernetes ingress controllers do automate configuration — they watch the Kubernetes API and regenerate nginx.conf, then trigger a reload. It's not manual. The real difference is that a reload restarts worker processes, while Envoy's xDS updates apply in-memory with no restart at all. At moderate rates of change this distinction barely matters; at high-churn scale, it does.
Core Architecture: Control Plane vs Data Plane
Envoy itself is only the data plane — the component that actually terminates connections and moves traffic. It needs a separate control plane to tell it what to do: which routes exist, which backends are healthy, which TLS certs to use. In production this is usually Istio, though it can be a custom xDS server or even static config for simple setups.
This split — declarative configuration vs. the component that executes it — isn't unique to Envoy. It's the same pattern Kubernetes uses everywhere: a Deployment manifest doesn't run a container, the Controller Manager and kubelet do. A Gateway or HTTPRoute object doesn't route a request, a Controller and Proxy do. Envoy is simply the proxy half of that equation, purpose-built to be programmed by a control plane rather than configured once and left alone.
xDS: How Envoy Gets Configured Dynamically
xDS stands for "x Discovery Service" — the family of APIs that let a control plane push configuration into Envoy live, without a restart. The "x" is a placeholder for several discovery services, each responsible for a different slice of Envoy's behavior:
| API | Full name | What it tells Envoy |
|---|---|---|
| LDS | Listener Discovery Service | Which ports/protocols to listen on |
| RDS | Route Discovery Service | How to route requests (path/host rules) |
| CDS | Cluster Discovery Service | What upstream service clusters exist |
| EDS | Endpoint Discovery Service | Which pod IPs currently back each cluster |
| SDS | Secret Discovery Service | TLS certs/keys to use |
A request resolves through these in order: a listener accepts it (LDS), a route decides where it goes (RDS), a cluster is selected (CDS), a live endpoint is picked (EDS), and TLS is applied if needed (SDS). Most control planes bundle all five into a single gRPC stream called ADS (Aggregated Discovery Service) to avoid ordering issues between them.
The practical effect: when a pod scales up or a route changes, the control plane streams an incremental update to every Envoy instance, and Envoy updates its internal state with zero dropped connections — no reload, no restart.
Deployment Patterns in Production
There isn't one "correct" way to run Envoy — most large clusters combine more than one of these patterns.
- Sidecar (service mesh) — an Envoy instance runs alongside every application pod, intercepting all inbound/outbound traffic. This is how Istio and AWS App Mesh work. It gives you mutual TLS, per-service retries, and fine-grained traffic splitting, at the cost of a proxy hop and resource overhead per pod.
- Edge / ingress gateway — a pool of Envoy instances sits at the cluster edge handling everything entering from outside. This is what Envoy Gateway, Contour, and Istio's ingress gateway do — TLS termination, external routing, and rate limiting at a single choke point.
- Standalone load balancer — Envoy deployed as a general-purpose LB, replacing Nginx/an ELB directly, without a control plane or sidecars involved.
- Ambient mesh (sidecar-less) — a newer pattern (Istio Ambient Mode) where a shared Envoy-based proxy handles mesh traffic per-node instead of per-pod, avoiding sidecar overhead.
A typical production setup: an edge gateway Envoy terminates external traffic, forwarding it into the cluster, where sidecar Envoys handle service-to-service traffic — both managed by one Istio control plane pushing xDS to everything.
Envoy and the Kubernetes Gateway API
The Kubernetes Gateway API is a vendor-neutral specification (GatewayClass, Gateway, HTTPRoute) that replaced the older Ingress resource. On its own, it's purely declarative — it stores desired state in etcd and does nothing on its own. Something has to read that state and act on it: that's the job of a Controller, and Envoy Gateway is one official implementation of that role, with Envoy itself doing the actual traffic handling.
Request workflow: config is applied → the Envoy Gateway controller watches and translates it into xDS → the Envoy proxy pod serves live traffic to backend pods
The flow looks like this:
- Config is applied —
GatewayandHTTPRouteobjects are submitted to the Kubernetes API server and stored in etcd. - The controller watches — the Envoy Gateway controller runs a continuous watch for any Gateway API object that references it.
- The controller translates config — Gateway API resources get converted into Envoy's xDS configuration.
- The controller pushes config to the proxy — delivered over a dynamic protocol, no restart required.
- The proxy serves live traffic — Envoy terminates connections, evaluates requests against the rules it received, and forwards matched traffic to the right backend
Service, which load-balances across its pods.
Envoy isn't the only Gateway API implementation — Istio, Contour, Kong, Traefik, and Cilium all implement it too, some using Envoy underneath and some using their own proxy engines.
Frequently Asked
If I already have Nginx, HAProxy, or an API Gateway — why would I need Envoy? You might not. Envoy solves problems specific to high-churn, cloud-native environments: dynamic reconfiguration without restarts, deep service-to-service traffic control, and serving as the data plane for a service mesh. If your setup is stable and mostly inbound traffic, your existing tools are likely fine.
If I'm using Nginx as my Gateway API controller, do I need to manually update configs?
No — the controller automates it. It watches the Kubernetes API and regenerates nginx.conf, then triggers a reload. The difference from Envoy is how that update applies: Nginx reloads worker processes (graceful, but a real event), while Envoy updates in memory via xDS with no process restart. This mostly matters at high rates of config change.
Is Envoy only for Kubernetes? No, but Kubernetes is where it's most commonly deployed today, since that's the environment where dynamic, API-driven configuration provides the biggest benefit.
Summary
Envoy's core contribution isn't that it proxies traffic — Nginx and HAProxy already do that well. It's that Envoy was built from the ground up to be programmed by a control plane, at the pace and scale of modern container orchestration, with deep observability and resilience features baked in rather than bolted on. Understanding the split between control plane (declarative config) and data plane (the proxy executing it) — and the xDS protocol that connects them — is the key to understanding not just Envoy, but the broader architecture behind the Kubernetes Gateway API and every service mesh built on top of it.