Skip to main content

IPVS Is Deprecated in Kubernetes 1.35: What Actually Changes When kube-proxy Moves to nftables

8 min readDora NodaDora Noda
Share
On this page

Kubernetes 1.35 shipped a warning nobody asked for: start kube-proxy in IPVS mode now, and it logs a deprecation notice on every boot. That's PR #134539, and it's not a "someday" signal — IPVS is on its way out, and nftables is the only mode upstream is telling you to move to. If your fleet's Services have been running IPVS mode for the throughput and the scheduling algorithms since the 1.11 days, this is the release that starts the clock.

Here's what changes, concretely, before you touch a single node.

What Actually Changes, at a Glance

IPVSnftables
Kernel requirementAny kernel with the ip_vs moduleLinux 5.13+ (kube-proxy fails to start below this on nftables mode)
Tooling requirementipvsadmnft CLI 1.0.1+
Load-balancing algorithmSelectable: rr, wrr, lc, wlc, lblc, lblcr, sh, dhFixed: randomized selection only — no scheduler field exists
Lookup complexityO(1), independent of cluster sizeO(1), independent of cluster size
Connection trackingSeparate ip_vs conntrack entries, on top of iptables' NAT rulesSingle netfilter conntrack path, no separate kernel module
Extra kernel moduleYes — ip_vs (plus scheduler submodules for wlc, sh, etc.)No — mainline nf_tables only
GA statusDeprecated as of 1.35GA since 1.33 (alpha 1.29, beta 1.31)

Two rows are the ones that actually bite an operator mid-migration: the algorithm row, because it's a tuning knob some fleets rely on and nftables mode doesn't have a replacement for it; and the kernel row, because "deprecated" doesn't mean "drop-in" — nftables mode simply won't start on a node running a kernel older than 5.13.

Why IPVS Couldn't Just Get Fixed Instead

It's worth a sentence on why upstream chose to retire IPVS rather than keep maintaining it alongside nftables, because it explains why there's no gentler middle path. The kernel's IPVS API was never a clean match for what a Kubernetes Service actually needs to express — session affinity edge cases, endpoint slicing, dual-stack — so the IPVS proxy backend has always fallen back to iptables rules for the parts IPVS itself can't do. That means "switch to IPVS" was never actually "stop using iptables"; it was "use iptables and a second kernel subsystem," which is exactly the maintenance burden nftables mode was built to collapse into one path. Once nftables matched IPVS's O(1) lookup performance without the second module, there was no version of "keep IPVS around" that didn't mean permanently maintaining two service-proxy implementations for one job.

The Load-Balancing Algorithm You Lose

This is the change most migration guides skip, and it's the one that actually requires a decision. IPVS exposes eight selectable scheduling algorithms for how a Service's traffic gets spread across its endpoints — rr (round robin), wrr (weighted round robin), lc (least connections), wlc (weighted least connections), lblc/lblcr (locality-based variants), and sh/dh (source/destination hashing). A fleet running mixed-size backend instances behind one Service — a common shape when node pools scale out with whatever capacity was available, not uniform SKUs — could set wlc and let IPVS route more connections to the beefier endpoints.

nftables mode has no equivalent field. There is no --ipvs-scheduler analog, no config knob, nothing to set. Endpoint selection is effectively randomized across ready endpoints, the same shape as iptables mode's "randomized equal cost" behavior. If a platform's Services depend on wlc or lc to route around uneven endpoint capacity, that tuning disappears the moment kube-proxy switches modes — not because it's misconfigured, but because the feature doesn't exist in the target backend. The fix isn't a config flag; it's redistributing load evenly across right-sized endpoints in the first place, or pushing weighted routing up a layer (a service mesh sidecar, or an L7 proxy in front of the Service) if uneven backend capacity is unavoidable.

Connection Tracking: Same Kernel Facility, Different Stress Pattern

Both modes ultimately rely on Linux's conntrack subsystem to avoid re-evaluating rules for packets belonging to an existing connection — a packet that matches an established conntrack entry skips the proxy's rules entirely. But IPVS and nftables get there differently. IPVS maintains its own connection table inside the ip_vs module in addition to the kernel's general conntrack table, and its scheduler decisions interact with that IPVS-specific state. nftables mode routes everything through the single, general-purpose conntrack path with no parallel bookkeeping layer.

The practical consequence during a migration: a workload with a high connection-churn rate (short-lived connections opening and closing rapidly — think a fleet fronting lots of small HTTP requests without keep-alive) can shift where conntrack pressure shows up once the second table goes away. Before flipping a node, check its headroom:

bash
# current entries vs. ceiling
conntrack -S
sysctl net.netfilter.nf_conntrack_count net.netfilter.nf_conntrack_max

If nf_conntrack_count is already tracking close to nf_conntrack_max under IPVS, that's a node to size up nf_conntrack_max on before the mode switch, not after connections start dropping.

The Rolling, Mixed-Mode-Safe Migration Sequence

The good news: this doesn't have to be a maintenance-window, whole-cluster cutover. Kubernetes' own service-proxy design guarantees no node needs to know or care what proxy mode any other node in the cluster is running — Service routing is a per-node implementation detail, not a cluster-wide contract. That makes a rolling, one-node-at-a-time migration the correct default, not just a convenience:

  1. Check kernel and nft versions on every node first. uname -r needs 5.13+; nft --version needs 1.0.1+. Do this cluster-wide before touching any config — a node that fails this check needs an OS/kernel bump queued before it's eligible for the mode switch at all.
  2. Install the nftables package on nodes that don't already have it (apt install nftables on Debian/Ubuntu, dnf install nftables on RHEL-family distros).
  3. Snapshot existing rules as a rollback reference: iptables-save > iptables-backup.rules and, if IPVS is active, ipvsadm -Sn > ipvs-backup.rules.
  4. Flip the mode field to nftables in the KubeProxyConfiguration (see the kubeadm snippet below) and restart kube-proxy on one node.
  5. Verify the new ruleset landed: nft list ruleset | grep -A2 kube-proxy should show populated chains; confirm Service ClusterIPs still resolve traffic to endpoints from that node before moving on.
  6. Repeat node by node. It's safe — and expected — to run a mixed fleet mid-migration, some nodes on IPVS, some on nftables, for as long as the rollout takes.
  7. Decommission the old rules once every node is confirmed on nftables. It's safe to delete leftover IPVS/iptables rules once kube-proxy starts in nftables mode, and safe to delete nftables rules if you ever roll a node back — the two rule sets don't need to coexist per-node, only across the fleet during the rollout window.

What a CAPH/kubeadm Fleet Needs to Set Explicitly

This is the part that doesn't apply the same way to a managed-Kubernetes customer. On EKS, GKE, or AKS, the mode a Service proxy runs in is something the vendor can (and often does) change on your behalf during a platform-managed upgrade — you find out from a changelog, not a decision you made. A fleet provisioned through Cluster API Provider Hetzner (CAPH) bootstraps nodes via kubeadm, and kubeadm does not silently flip an existing cluster's proxy mode on upgrade. It stays exactly where it was set, forever, until an operator changes the KubeProxyConfiguration explicitly:

yaml
apiVersion: kubeadm.k8s.io/v1beta4
kind: ClusterConfiguration
yaml
apiVersion: kubeproxy.config.k8s.io/v1alpha1
kind: KubeProxyConfiguration
mode: nftables
nftables:
  masqueradeAll: false
  masqueradeBit: 14
  minSyncPeriod: 1s
  syncPeriod: 30s

For a brand-new cluster, there's nothing to migrate — set mode: nftables in the KubeadmControlPlane's kubeadmConfigSpec from the start and every node boots into the target state directly. For an existing fleet, this config change is the trigger for the rolling sequence above: it's a KubeadmConfigTemplate/KubeadmControlPlane edit that gets rolled out node by node, not a flag Hetzner or CAPH will ever flip for you. Owning the nodes means owning this migration on your own schedule — which is the tradeoff either way, and the reason to do it now, deliberately, while IPVS still runs as a working fallback on any node that hasn't been converted yet.

Do This Before Removal Picks the Timeline

IPVS isn't gone in 1.35 — it's marked, and marking is the warning shot before an eventual future release removes it outright the way Kubernetes has retired other deprecated proxy paths before. A node still on IPVS today keeps working. The advantage of moving now, one node at a time, is that every step above — the kernel check, the conntrack headroom check, the algorithm gap, the config rollout — happens on a schedule you control, with the old path still available to fall back to if something doesn't verify cleanly. Wait for a forced removal release instead, and the same checklist becomes a compressed, no-fallback scramble across however many nodes are still on the deprecated path. (Ingress-NGINX reaching best-effort-only maintenance the same release cycle is a separate networking-layer story worth its own read — it doesn't change anything about this migration, but it's a reminder that a self-managed fleet's networking stack gets more than one upstream deprecation notice per cycle, not zero.)

Bex.co is the open-source, AI-native Render alternative — push a git repo, get a running HTTPS service on machines you own, provisioned through the same Cluster API + kubeadm stack this migration runs on. Star the repo on GitHub or deploy your first app today.

Sources

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