What we faced after removing the sidecars — where does the overhead of mTLS and L7 policies shift to in Cilium Service Mesh
When I started reviewing Cilium Service Mesh, the first thing I wanted to confirm was "so does Envoy really disappear?" To cut to the conclusion: the sidecars that used to attach to each pod do disappear, but Envoy itself remains as one per node, and the mTLS data plane is still handled by IPsec/WireGuard. Rather than "zero proxy," it's closer to actual behavior to understand it as an architecture where the proxy has moved from the pod level to the node level.
This article, based on experience operating hundreds of services with the Istio sidecar model, aims to examine what moves where when switching to Cilium Service Mesh. In particular, I want to focus on how L7 policy density transfers to node Envoy load, at which layer mTLS is handled, and what points are frequently missed in kernel/policy combinations.
As of August 2026, Cilium's 1.1x line is being released on the stable track (check the exact release tags at GitHub Releases), and Mutual Authentication is still Beta according to the official documentation. As for whether Cilium will adopt the ztunnel component from the Istio Ambient camp, discussion remains at the support-request/discussion stage in Issue #38548, so this article also labels it as "experimental/discussion" rather than "adopted."
Facts to confirm before we start
While re-reviewing the draft, I organized the points that are commonly misquoted. The following are premises for this entire article.
- Cilium's mTLS data-plane encryption is handled by IPsec or WireGuard, and Mutual Authentication performs SPIFFE/SPIRE-based identity verification out-of-band (official docs). The description that "TLS 1.3 is terminated inline pod-to-pod" is not a standard formally adopted by Cilium; it's the approach of the Ambient/ztunnel family.
- ztunnel is a component of Istio Ambient, and is not incorporated into Cilium core. Related discussion continues at Issue #38548, and it's safer to read it as roughly "experimental integration."
- Minimum kernel requirements vary by feature. Cilium itself requires 4.19+, while XDP, BPF LSM, and certain session affinity features require 5.x–6.x (System Requirements). The simplification "mTLS mutual authentication requires kernel 5.4+" has no basis, so this article does not use it.
Where L7 policy and mTLS are actually processed
Processing paths branch by policy type
Cilium separates and handles three layers.
- L3/L4 policy and load balancing: eBPF programs process in the kernel
- L7 policy (HTTP/gRPC/Kafka): the kernel redirects to the per-node embedded Envoy
- Encryption/identity: transport encryption via IPsec/WireGuard, identity verification via SPIFFE SVID
The reason this branching matters: the more L7-policied services are concentrated on a node, the greater the Envoy resource consumption; conversely, if traffic is L4-dominant, the Envoy path itself does not exist. In other words, the judgment "Cilium is fast/slow" depends on the cluster's L7 policy density.
Workload identity extends to SPIFFE
Cilium maps SVIDs of the form spiffe://<trust-domain>/ns/<namespace>/sa/<service-account> to its own Security Identity. Because it uses the ServiceAccount as the root of identity, it naturally connects with the existing RBAC model, and the SPIRE server handles issuance without application code changes (Mutual Authentication docs).
To emphasize again: this Mutual Authentication is in Beta status. For services with production SLAs, this part in particular should be validated carefully.
Actually layering on L7 policy, mTLS, and Gateway API
HTTP method/path filtering
An example that restricts frontend to calling only GET /api/v1/... on backend. Cilium HTTP rules' path is evaluated as a regular expression, so the below allows all paths under /api/v1/ (HTTP filtering reference).
apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
name: backend-l7-policy
namespace: shop
spec:
endpointSelector:
matchLabels:
app: backend
ingress:
- fromEndpoints:
- matchLabels:
app: frontend
toPorts:
- ports:
- port: "8080"
protocol: TCP
rules:
http:
- method: "GET"
path: "/api/v1/.*"The moment this policy is applied, the target traffic is redirected by an eBPF program to the node Envoy. If only L3/L4 policies exist, it terminates in the kernel without going through Envoy. The point that a single line of policy syntax changes the number of data-plane hops is a decisive difference from the sidecar model.
A policy that requires mTLS
Mutual Authentication is enabled by attaching an authentication block under a CNP ingress rule. It's safer to verify the exact placement in the schema against the CRD of the Cilium version in use (see the official docs for detailed examples).
apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
name: backend-mtls-required
namespace: shop
spec:
endpointSelector:
matchLabels:
app: backend
ingress:
- fromEndpoints:
- matchLabels:
app: frontend
authentication:
mode: "required"
toPorts:
- ports:
- port: "8080"
protocol: TCPWhen authentication.mode: required is attached, payload only flows after the Cilium agent performs an out-of-band handshake with the SVID issued by SPIRE. Keep in mind that encryption of the traffic itself is handled by the separately enabled IPsec/WireGuard layer.
Conceptual flow of traffic with an L7 policy applied
The sequence below is a simplified picture of "the conceptual order a request goes through when both an L7 policy and Mutual Authentication are applied at the same time." In actual implementation, the detailed steps may vary depending on the CiliumEnvoyConfig redirection order and the policy combination.
Gateway API as ingress controller
One of the backgrounds behind recent increased adoption is that you can use Gateway API directly without a separate Ingress NGINX. In the example below, if the gateway in parentRefs is in a different namespace, the namespace field must be specified.
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: shop-route
namespace: shop
spec:
parentRefs:
- name: cilium-gateway
# If the gateway is in a different namespace, add the field below
# namespace: infra
hostnames:
- "shop.example.com"
rules:
- matches:
- path:
type: PathPrefix
value: /api
backendRefs:
- name: backend
port: 8080Cilium reflects this routing into the node Envoy as a CiliumEnvoyConfig. It's a structure where an L7 gateway is established without layering on a separate Envoy Gateway.
Re-reading the points where latency and memory change
Look at the conditions before quoting numbers
Service mesh benchmarks swing greatly depending on workload, RPS, payload, and whether tuning was applied. Rather than copying specific figures, it's better to check the original measurement conditions together.
| Observation Item | Where to Look | Cautions When Checking |
|---|---|---|
| Pod/node proxy memory | Each project's release notes, own profiling | Number of sidecars vs. node Envoy multiply on different axes |
| P50/P99 latency | Buoyant benchmarks, arXiv search | RPS, payload, protocol (gRPC/HTTP) conditions essential |
| Per-hop overhead | Individual project docs, CNCF reports | The path itself changes depending on whether L7 policy is applied |
That is, before drawing the conclusion "Cilium is fast" from the table, you should first understand your own cluster's L7 policy ratio, average payload, and whether gRPC streaming is used. If sidecar-model overhead is linear in the number of pods, Cilium-mode overhead draws a curve closer to "policy density × per-node traffic concentration."
What node Envoy experiences when L7 policy piles up
This is the point to be most careful about. When L7 policy concentrates on a specific node, the load that used to be distributed per pod in the sidecar era condenses into a single node Envoy process. The following items are curves you must draw at the capsule level before adoption.
- Envoy RSS/CPU vs. (policy count × active connection count)
- P99 latency vs. HTTP filter chain length
- Connection re-establishment due to Envoy cold start on node restart
Without drawing these curves in advance, per-node hotspots that were hard to see in the sidecar model get observed belatedly in production.
Practical issues that make adoption worth reconsidering
Kernel requirements must be checked first. Cilium requires 4.19+ as a minimum, but XDP acceleration, BPF LSM, and certain session affinity features assume 5.x or later (System Requirements). If you're using older node images on-prem, you should sort out kernel policy before making an adoption decision.
The expression "completely sidecar-less" differs from the implementation details in nuance. L7 processing and TLS termination are not completed in the kernel alone; a user-space component called node Envoy remains. Since the proxy that disappeared from the pod moved to the node level, on nodes where L7 policy and TLS termination pile up, CPU spikes and eBPF map size management pass over as new homework.
The debugging tool stack changes. If you cling only to tcpdump and iptables, it's hard to trace the cause of packets dropped at the eBPF level. Generally, the Hubble and Tetragon combination effectively becomes mandatory, and depending on organization size, you'll end up evaluating even commercial observability stacks.
Always re-verify CVEs/advisories in the release notes. The eBPF data plane has points where, separately from policy expression, policy enforcement can vary depending on the combination of data-plane settings. Before quoting a specific advisory ID, it's a good habit to establish a procedure of directly checking the affected version range, CVSS, and mitigation methods on the Cilium Security Advisories page.
When is it better to keep the sidecar model
Decision-making has kernel requirements as a prerequisite. Next is L7 policy density, and then node density in order.
To summarize, the pattern that is repeatedly observed is that the more pods and the more L4-dominant traffic, the greater the gain from eBPF mode; conversely, the more L7 policy concentrates on specific nodes in a workload, the more node Envoy can become a new bottleneck.
Closing
There is only one point I want to argue strongly in this article. Service mesh load does not disappear; it moves. The expression "removed the sidecar" means "it disappeared from the pod," not that it disappeared from the entire cluster. Just shifting the focus of the adoption decision from "eliminating Envoy" to "which layer to move L7 processing and TLS termination to, and how to size the capacity of that layer" prevents half of post-incident issues.
Based on an environment at the 500-service scale with a low L7 policy ratio and L4-dominant traffic, I expect I would prioritize Cilium mode for the next cluster as well. However, if workload characteristics differ, the conclusion should differ too. I recommend following the four steps in the order kernel requirements → L7 policy density → node concentration → observation/debugging stack readiness for judgment.
References
- Cilium Releases (GitHub)
- Mutual Authentication (Beta) — Cilium Docs, stable
- Gateway API Support — Cilium Docs, stable
- System Requirements — Cilium Docs, stable
- HTTP Policy Language — Cilium Docs, stable
- Cilium Security Advisories (GitHub)
- Add support for ztunnel · Issue #38548 — cilium/cilium
- Hubble — Cilium Observability
- Tetragon Official Site
- Buoyant Blog
- Introducing Istio Ambient Mesh