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
| IPVS | nftables | |
|---|---|---|
| Kernel requirement | Any kernel with the ip_vs module | Linux 5.13+ (kube-proxy fails to start below this on nftables mode) |
| Tooling requirement | ipvsadm | nft CLI 1.0.1+ |
| Load-balancing algorithm | Selectable: rr, wrr, lc, wlc, lblc, lblcr, sh, dh | Fixed: randomized selection only — no scheduler field exists |
| Lookup complexity | O(1), independent of cluster size | O(1), independent of cluster size |
| Connection tracking | Separate ip_vs conntrack entries, on top of iptables' NAT rules | Single netfilter conntrack path, no separate kernel module |
| Extra kernel module | Yes — ip_vs (plus scheduler submodules for wlc, sh, etc.) | No — mainline nf_tables only |
| GA status | Deprecated as of 1.35 | GA 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:
# current entries vs. ceiling
conntrack -S
sysctl net.netfilter.nf_conntrack_count net.netfilter.nf_conntrack_maxIf 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:
- Check kernel and
nftversions on every node first.uname -rneeds 5.13+;nft --versionneeds 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. - Install the
nftablespackage on nodes that don't already have it (apt install nftableson Debian/Ubuntu,dnf install nftableson RHEL-family distros). - Snapshot existing rules as a rollback reference:
iptables-save > iptables-backup.rulesand, if IPVS is active,ipvsadm -Sn > ipvs-backup.rules. - Flip the
modefield tonftablesin theKubeProxyConfiguration(see the kubeadm snippet below) and restartkube-proxyon one node. - Verify the new ruleset landed:
nft list ruleset | grep -A2 kube-proxyshould show populated chains; confirm Service ClusterIPs still resolve traffic to endpoints from that node before moving on. - 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.
- 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:
apiVersion: kubeadm.k8s.io/v1beta4
kind: ClusterConfigurationapiVersion: kubeproxy.config.k8s.io/v1alpha1
kind: KubeProxyConfiguration
mode: nftables
nftables:
masqueradeAll: false
masqueradeBit: 14
minSyncPeriod: 1s
syncPeriod: 30sFor 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
- Kubernetes v1.35: Timbernetes (The World Tree Release)
- NFTables mode for kube-proxy
- Virtual IPs and Service Proxies
- KEP-3866: Add an nftables-based kube-proxy backend
- Linux Kernel Version Requirements
- kubernetes/kubernetes PR #134539 — Mark IPVS proxy mode as deprecated
- From IPVS to NFTables: A Migration Guide for Kubernetes v1.35 — Tigera
- kubeadm Configuration (v1beta3) — KubeProxyConfiguration



