This episode covers Envoy's role at the edge of the architecture: as an edge proxy and API gateway with virtual hosts, rate limiting, authentication, CORS, and request transformation, plus gateway versus internal sidecar considerations.

In episode 12, Envoy secured communication between services inside the network. Episode 13 shifts the focus to the edge: Envoy as an edge proxy and API gateway — a single entry point for external clients before traffic reaches the microservices. This role combines everything you've learned: virtual hosts for many domains, rate limiting, authentication at the gateway, CORS for browsers, and request transformation.
As an edge proxy, Envoy stands at the boundary of the architecture: all external clients talk to a single address, then Envoy routes to internal services. The API gateway pattern adds cross-service policies at this point: rate limits, authentication, and transformation apply to all APIs.
static_resources:
listeners:
- name: edge_listener
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: edge_gateway
route_config:
name: gateway_routes
virtual_hosts:
- name: public_api
domains:
- api.example.com
routes: []
http_filters:
- name: envoy.filters.http.cors
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.cors.v3.Cors
- name: envoy.filters.http.router
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.RouterThis pattern puts all public policies in a single edge_listener listener. The public_api virtual host serves the public domain, and the CORS filter is installed because gateway clients are often browsers.
One sentence to distinguish them: a gateway serves inbound traffic from outside, while a sidecar serves traffic between internal services. Both are Envoy with different configurations — one technology filling two roles.
A public gateway usually serves several domains and subdomains at once:
virtual_hosts:
- name: orders_api
domains:
- api.orders.example.com
routes:
- match:
prefix: "/v1/"
route:
cluster: orders_v1
typed_per_filter_config:
envoy.filters.http.ratelimit:
"@type": type.googleapis.com/envoy.extensions.filters.http.ratelimit.v3.RateLimitPerRoute
vh_rate_limits:
- actions:
- generic_key:
descriptor_value: orders_v1
- name: auth_api
domains:
- api.auth.example.com
routes:
- match:
prefix: "/"
route:
cluster: auth_serviceEach virtual host has its own typed_per_filter_config — here, a different rate limit rule per domain. The gateway is the right place for policies that differ between products.
The /v1/, /v2/ routing pattern lets multiple API versions live side by side. Combine it with weighted clusters from episode 18 for gradual migration from an old version to a new one.
Rate limiting at the edge is the first line of defense against API abuse. Combine the rate limit filter with per-route rules:
http_filters:
- name: envoy.filters.http.ratelimit
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.ratelimit.v3.RateLimit
domain: edge-api
rate_limit_service:
grpc_service:
envoy_grpc:
cluster_name: ratelimit_service
failure_mode_deny: true
- name: envoy.filters.http.router
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Routerfailure_mode_deny: true changes the behavior when the rate limit service fails: requests are denied instead of letting them through without limits. For public APIs vulnerable to abuse, deny mode is safer.
A frontend web application needs CORS headers so the browser allows cross-domain calls:
virtual_hosts:
- name: public_api
domains:
- api.example.com
cors:
allow_origin_string_match:
- prefix: "https://app.example.com"
allow_methods: GET, POST, PUT, DELETE, OPTIONS
allow_headers: authorization, content-type, x-request-id
max_age: "86400"The cors block defines allowed origins, methods, and headers. The value allow_origin_string_match is safer than a wildcard because it restricts to the specific frontend application origin.
The gateway is the place for centralized authentication: the client brings a token, Envoy verifies it once at the entrance, and internal services don't need to repeat that. JWT filter details are covered in episode 14, but the basic pattern: verify the token at the edge, forward identity via headers.
request_headers_to_add:
- header:
key: X-Client-Identity
value: "%EXT_AUTHZ(RESULT:app_context.client_id)%"
append_action: APPEND_IF_EXISTS_OR_ADDThe example above takes the client identity from the ext_authz filter result and forwards it as a header. With this pattern, internal services just trust the X-Client-Identity header from their internal gateway.
Gateways often transform requests before forwarding: adding headers, rewriting paths, or removing sensitive headers. All these techniques were covered in episode 4, and at the edge they become part of the API's public "contract".
Many architectures use both at the same time:
The gateway handles public concerns: authentication, rate limits, CORS, and transformation. The sidecar handles internal concerns: inter-service routing, retries, and communication security.
curl -s localhost:9901/listeners
curl -s -o /dev/null -w "%{http_code}\n" -H "Origin: https://app.example.com" \
-H "Host: api.example.com" http://localhost:10000/api/pingThe curl -o /dev/null -w "%{http_code}" command with an Origin header tests the CORS response for a cross-domain request.
Episode 13 positioned Envoy at the edge of the architecture: an edge proxy and API gateway with multi-domain virtual hosts, public rate limiting, centralized authentication, CORS for browsers, and request transformation.
Key takeaways:
failure_mode_deny secures public APIs.In the next episode, episode 14, we'll discuss filters for security enforcement — external authentication with ext_authz, the JWT filter, HTTP authorization filters, and content-based routing and request inspection.