Skip to main content

Cilium 1.20 Retires Two More Boxes: TCP Routes, Gateway Auth, and the Case for a CNI-Run Fleet

10 min readDora NodaDora Noda
Share
On this page

In March 2026, the Kubernetes project archived ingress-nginx — the ingress controller a generation of small platforms grew up on — after a run of critical CVEs that included a 9.8 unauthenticated remote-code-execution bug. Six months later, on September 14, Cilium 1.20 landed and closed the last two gaps that kept teams bolting extra boxes next to their CNI: Layer 4 routing and gateway-layer authentication. If you run a small self-hosted fleet, those two events belong in the same sentence, because together they settle an argument: the CNI can now be the whole networking control plane.

The short version up front: Cilium 1.20's Gateway API support (now at v1.6) absorbs plain-TCP/UDP services through TCPRoute/UDPRoute and gateway authentication through the ExternalAuth filter — the two workloads that previously forced you out of the Gateway API model into raw LoadBalancer Services and a separate auth proxy. Combined with ListenerSets for namespace-delegated listeners and BackendTLSPolicy for backend encryption, a fleet that already runs Cilium can retire its ingress controller, its auth sidecar, and its hand-maintained L4 Services, leaving one upgrade train instead of three.

The migration is real work — ingress2gateway 1.0 does the mechanical translation but drops vendor-specific annotations, and TCPRoute needs the v1.6 CRD bundle — and a service mesh still earns its keep for L7 authorization nuance, retries, and multi-cluster identity. The consolidation map is in the second section; the honest costs are in the fourth.

What 1.20 actually ships (and which box each feature retires)

Cilium 1.20 is the second major Cilium release of 2026, and its headline theme is Gateway API growing from an HTTP-routing API into a general traffic-management layer. Here is the release mapped against the boxes it lets you switch off:

1.20 featureMaturityWhat it retires
TCPRoute / UDPRoute (GEP-2644/2645)Standard channel in Gateway API v1.6Raw LoadBalancer/NodePort Services for databases, DNS, brokers, game servers
ExternalAuth filter (GEP-1494)ExperimentalSeparate auth proxy (Envoy-with-ext-authz sidecar, oauth2-proxy hop) in front of the gateway
ListenerSetsGA (Standard since v1.5)Shared-gateway ticket queues — app teams attach listeners from their own namespaces
BackendTLSPolicyNew in Cilium 1.20Hand-rolled backend TLS config between gateway and services
HTTPRoute CORS filterStandardCORS handled in-app or in a sidecar
cilium-cni binary 76 MB → 16 MBShippedNothing retired — but a 79% smaller install artifact on every node
Datapath plugins (CiliumDatapathPlugin)BetaVendor Cilium forks (cloud-provider eBPF extensions without diverging builds)
ENI IPAM with IPv6BetaThe last IPv4-only gap on AWS (built by Datadog; closes a four-year-old feature request)

Two rows deserve a closer look, because they carry the "control plane, not just CNI" thesis.

TCPRoute/UDPRoute deliberately route around Envoy. Cilium's implementation does not push L4 traffic through its Envoy-based L7 proxy. Instead the Gateway API operator reconciles L4 routes into LoadBalancer Services plus operator-managed EndpointSlices that Cilium's eBPF service load-balancer consumes directly, including backend-weight propagation. That is one fewer data-plane hop for every TCP connection and it means L4 routing inherits the eBPF datapath's performance rather than paying Envoy's. A weight of 0 marks backends as in maintenance — existing connections keep working while new ones stop arriving — which is a connection-preserving drain primitive you previously built yourself.

ExternalAuth moves the auth decision in front of the app. The filter lets the platform team declare an external authorizer as part of the Gateway configuration: SSO redirects for browsers, JWT validation for service-to-service calls, access gating for CI/CD agents — all enforced before a request reaches application code, all managed in the same API as the routes. Before 1.20, gateway-layer auth meant operating a separate proxy with its own config language, upgrade cadence, and failure modes.

Two precision notes before anyone quotes this post back at me. First, the "79% smaller" number that headlines this piece is the cilium-cni plugin binary — the thing the kubelet execs from /opt/cni/bin to wire up each pod — which had accreted agent imports until it weighed 76 MB in 1.19 and is 16 MB in 1.20. It is not the agent itself. It still matters (smaller image layers, faster CNI installs on every node), but say it right.

Second, ENI IPv6 and datapath plugins are AWS- and cloud-provider-shaped features; on a bare-metal Hetzner fleet they are spectators, not reasons to upgrade. The upgrade reasons are the Gateway API rows.

The before/after stack for an ingress-nginx-era fleet

Picture the modal small fleet in early 2026: Cilium as CNI, ingress-nginx as the ingress controller, Kubernetes NetworkPolicies (or CiliumClusterwideNetworkPolicy for cluster-wide default-deny) for segmentation, raw Services of type LoadBalancer for the odd TCP workload, and something hand-grown for auth at the edge. That is three upgrade trains — CNI, ingress controller, auth proxy — plus annotation folklore. ingress-nginx's retirement notice turned one of those trains into a liability with no future security patches, and its would-be successor never reached production quality, so the sanctioned path is Gateway API, not a different ingress controller.

After a 1.20 consolidation, the same fleet looks like this:

  • One Gateway, three route kinds. HTTPRoute for web traffic, TCPRoute for Postgres/Redis/brokers, UDPRoute for DNS — all behind one Gateway address, each on its own port, one kubectl get away from each other.
  • Auth declared, not deployed. The ExternalAuth filter references the authorizer; there is no extra Deployment to size, monitor, and upgrade.
  • Policy in one place. Cluster-wide segmentation stays in CiliumClusterwideNetworkPolicy, which exists precisely for whole-fleet enforcement, while per-app rules stay namespaced. The gateway's L7 proxy identity (reserved:ingress) gets its explicit allow-rule and everything else defaults to deny.
  • Delegation without tickets. ListenerSets let app teams attach listeners from their own namespaces against the platform-owned Gateway — the multi-tenant ergonomics that previously required either a shared-gateway change process or a Gateway per team.

The honest accounting: you trade three components for one, but the one is bigger and newer. Gateway API v1.6 CRDs, Cilium 1.20's operator paths for L4, and an experimental auth filter are all younger than the ingress-nginx config they replace. That is the right trade once the old component is unmaintained — archived software with a 9.8 in its rear-view mirror is not "battle-tested," it is frozen — but go in with upgrade-runbook discipline, not vibes.

Migration cost, concretely

This is the section that decides whether the consolidation happens this quarter or "someday." Four line items:

1. Translating Ingress objects: mostly mechanical. ingress2gateway 1.0 shipped March 20, 2026 — four days before the archive notice — and converts existing Ingress resources to Gateway API resources either from the live cluster or from files, with Cilium as a supported output provider. It handles 30+ ingress-nginx annotations.

What it cannot do is translate vendor-specific behavior into portable semantics: rewrite-target regexes, auth-request snippets, rate-limit annotations, and custom server-snippet blocks need rewriting as Gateway API filters or Cilium-specific extensions, per controller. Field reports converge on the same advice: the migration breaks on the annotations nobody checked, not on the YAML. Run the tool, diff every route, and keep both controllers running behind weighted DNS or a test Gateway until the new path proves itself.

2. The CRD floor: Gateway API bundle 1.6 or later. TCPRoute and UDPRoute reach v1 only in the 1.6 bundle; the 1.5 line serves them as v1alpha2, which Cilium 1.20's L4 support does not accept. Cilium reports the route kinds in GatewayClass.status.supportedFeatures either way (it reflects the compiled-in scheme, not your installed CRDs), so that field is not evidence the feature works. Verify with kubectl get tcproutes.v1.gateway.networking.k8s.io after upgrading the CRDs and before migrating a single L4 workload. This is the single most likely "it installed fine but L4 doesn't reconcile" trap.

3. Host-network Envoy mode rejects L4. If you run Cilium's Envoy in host network mode, TCP/UDP listeners and routes are rejected with status updates rather than reconciled. Check your deployment mode before promising anyone a TCPRoute cutover date.

4. Policy parity and the deprecation list. Re-validate NetworkPolicy behavior under the new version the same way you would for any datapath-affecting upgrade — 1.20 is a big release and policy is the thing you least want to discover by incident.

Then work the deprecation list from the release notes: legacy mutual authentication is deprecated with removal planned (ztunnel is the declared successor), and the on-agent cilium-dbg bgp subcommands plus the local REST BGP API are deprecated in favor of the hive-shell equivalents, so any runbook or monitoring that shells into the agent for BGP state needs updating. Budget an afternoon for the checklist, not a sprint — but budget it.

Where a mesh still earns its keep

"CNI as control plane" is not "mesh is dead," and a post that pretended otherwise would be doing its readers no favors. Here is the boundary as 1.20 draws it:

  • L7 authorization nuance. ExternalAuth answers "is this caller allowed in" at the gateway. It does not answer "is this service allowed to call that method on that service" deep inside the mesh — per-route, identity-aware authorization between workloads is still mesh (or Cilium L7 policy + mutual auth) territory.
  • Resilience policy. Retries, timeouts, circuit breaking, outlier ejection, and fine-grained traffic splitting for canaries live above what Gateway API routes express. If your platform offers tenants progressive delivery or fault injection, that stays a mesh-shaped problem.
  • Identity beyond the gateway. Gateway auth terminates at the edge; workload-to-workload mTLS identity (SPIRE-issued, SAN-bound, rotated automatically) is a different primitive from "the gateway checked a JWT." Cilium's own direction here is ztunnel-style ambient identity, which is mesh architecture wearing an eBPF costume — consolidation of the data plane, not elimination of the concept.
  • Multi-cluster. Gateway API ListenerSets delegate within a cluster. Cross-cluster failover, global load balancing, and multi-cluster service identity remain unsolved by any single-cluster CNI feature.

The practical rule: if every workload's security and routing questions can be answered at the cluster edge, 1.20 lets the CNI answer them. The moment tenants need per-call policy between services, you are shopping for a mesh (or Cilium's mesh-flavored features) with open eyes rather than discovering the gap mid-migration.

The takeaway for a small fleet

Cilium 1.20 plus the ingress-nginx archival make this a forcing function with a deadline that already passed: the old ingress stack is unmaintained, and the CNI you already run now speaks HTTP, TCP, UDP, auth, and backend TLS through one API.

For a Cluster-API-managed fleet on owned hardware, the consolidation removes an entire component class — no separate ingress controller to patch, no auth proxy to size, no raw L4 Services drifting outside the route model — in exchange for a bounded migration (ingress2gateway translation, CRD floor, policy re-validation) and a clear-eyed list of what still belongs to a mesh.

Start with the CRD upgrade and a shadow Gateway, migrate one low-risk HTTPRoute first, then bring L4 workloads over once TCPRoute reconciles cleanly. The mesh question can wait until a tenant asks for something the edge cannot express — which, after 1.20, is a much shorter list than it was.

Bex.co is the open-source, AI-native Render alternative — push a git repo, get a running HTTPS service on machines you own. Star the repo on GitHub or deploy your first app today.

Related articles

Run this on infrastructure you own

bex is the open-source, AI-native Render alternative — push a git repo and get a running HTTPS service on your own machines.

Get started with bex