When to Use Envoy’s Lua Filter: A Practical Guide for Request Manipulation
Envoy’s Lua filter lets you embed lightweight scripts to modify requests and responses on the fly. This guide shows how to add a custom header, measures the performance impact, and explains when the filter is a good fit and when to avoid it.
18 Jun 2026, 07:54 UTC

Problem: Custom Request Logic Inside the Proxy
In many micro‑service architectures we need to tweak HTTP traffic on the fly – add headers, rewrite URLs, or enforce per‑tenant policies – without touching downstream services. A common pattern is to push that logic into the API gateway or service mesh. Envoy’s lua filter lets you embed small scripts that run for every request, giving you a flexible, zero‑deployment‑change way to perform those tweaks.
Thesis: The Lua filter is great for lightweight, stateless transformations but should not replace a dedicated service for heavy logic.
The filter runs in a sandboxed Lua interpreter, so you get a familiar language without exposing the host. It can read and modify request and response headers, body, and metadata. However, because the interpreter is invoked per request, there is a measurable CPU cost (≈0.5–1.5 ms on modest hardware). The trade‑off is between the convenience of inline scripts and the latency impact in a high‑throughput path.
How to Add a Custom Header with Lua
Below is a minimal Envoy configuration that injects a header X‑Tenant‑ID based on a query parameter. The Lua script is loaded in the config block and runs before routing.
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:
name: local_route
virtual_hosts:
- name: local_service
domains: ["*"]
routes:
- match: { prefix: "/" }
route: { cluster: service_cluster }
http_filters:
- name: envoy.filters.http.lua
typed_config:
'@type': type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua
inline_code: |
function envoy_on_request(request_handle)
local tenant = request_handle:headers():get("tenant")
if tenant then
request_handle:headers():add("X-Tenant-ID", tenant)
end
end
- name: envoy.filters.http.router
To test, run:
envoy --config-yaml <config-file> --service-cluster demo --service-node demo_node &
curl -H "tenant: 42" http://127.0.0.1:10000/health
# Inspect the response headers – X‑Tenant‑ID: 42 should appear.
Performance Considerations
- CPU overhead: Each Lua execution adds ~0.5–1.5 ms latency on a 2‑core VM. In a 10 kreq/s workload that’s ~5–15 % extra CPU.
- Memory usage: Scripts that buffer the body can consume significant memory; keep body‑modifying logic to a minimum.
- Global state: Lua’s global table persists across requests; accidental globals can cause race conditions in Envoy’s multi‑threaded workers. Use
lua_shared_dictonly when you need cross‑request state and clear the dictionary on shutdown.
To quantify the impact, run a benchmark with wrk:
wrk -t4 -c200 -d30s http://127.0.0.1:10000/ > no_lua.txt
wrk -t4 -c200 -d30s http://127.0.0.1:10000/ > with_lua.txt
# Compare average latency in each file.
When to Avoid the Lua Filter
Heavy business logic, complex stateful operations, or high‑throughput paths should be handled by dedicated services or compiled filters (e.g., HTTP filters written in C++). Lua is best suited for:
- Header manipulation
- Simple auth checks (e.g., token extraction)
- Debug logging or metrics enrichment
- Feature flag injection
Actionable Checklist
- Identify a lightweight transformation that does not require body parsing or heavy computation.
- Write a stateless Lua script and test it locally with
curlandwrk. - Measure latency and CPU impact; keep overhead below 1 ms per request for most workloads.
- Deploy the filter into a staging environment and monitor
lua.logfor errors. - If the script needs shared state, use
lua_shared_dictand clear it on shutdown. - In production, enable
--log-level debugonly when troubleshooting; otherwise useinfoto avoid log noise.
By following this workflow, you can quickly add request‑time logic without changing your services, while keeping an eye on the performance cost.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.