Skip to main content
BlogDevOps

kube-proxy ipvs Deprecated: Migrate to nftables by v1.40

kube-proxy ipvs mode is deprecated: off by default in v1.40, removed in v1.43. A sourced nftables migration plan with kernel checks, config and breakage.

Matthews Wong

Software Engineer

October 202612 min read
kube-proxy ipvs Deprecated: Migrate to nftables by v1.40

Key Takeaway

kube-proxy ipvs mode is deprecated. The Kubernetes docs say it is disabled by default from v1.40, still selectable through the KubeProxyIPVS feature gate, and removed in v1.43. The recommended replacement is nftables mode, which needs Linux kernel 5.13 or later. Migrating means changing the mode, restarting kube-proxy, and checking NodePort, firewall and conntrack behaviour.

The sentence that should make an ipvs operator stop is short. The Kubernetes v1.37 release post says clusters running kube-proxy in ipvs mode now log a deprecation warning on startup, and then gives a timeline that ends with the code being deleted. Nothing breaks today. The risk is that nothing breaks today, so the migration sits in a backlog until a version upgrade turns it into an outage.

I read the primary sources end to end: the v1.37 sneak peek and release posts, the Kubernetes virtual-IPs documentation, the kube-proxy configuration reference, KEP-5495 (the deprecation) and KEP-3866 (the nftables backend), plus the 2025 kube-proxy nftables post. Every date, flag and metric name below comes from one of them. I did not run a benchmark, so there are no performance numbers of mine in this post, and the places where the sources are silent are marked as such.

The ipvs timeline, version by version

Three Kubernetes documents state the schedule: the virtual-IPs docs page, the v1.37 release post and KEP-5495. They agree on the end points. They differ on when the warning began, which matters if you are searching old logs.

ReleaseWhat happens to ipvs mode
v1.35kube-proxy prints a warning when started in ipvs mode and the website carries a deprecation notice (KEP-5495, stage 1). The docs page marks the mode as Deprecated since v1.35.
v1.37A KubeProxyIPVS feature gate is added at GA with default true. The release post says clusters in ipvs mode now log a deprecation warning on startup.
v1.40The gate flips to default false. Start kube-proxy in ipvs mode without overriding it and kube-proxy exits with an error listing the valid modes, iptables and nftables.
v1.43The ipvs code (pkg/proxy/ipvs) is removed from the tree and the remaining mentions leave the docs.
v1.46The KubeProxyIPVS feature gate itself is removed.

Some write-ups date the warning to v1.37; the KEP and the docs page put it at v1.35, and the release post only says the warning is logged now. Treat v1.40 as the date to be finished by, not the date to start. Until then the escape hatch is a feature gate, and a gate built to be flipped is not a place to live. The release post words the v1.40 and v1.43 steps as expectations, so re-read the docs page for your minor version before you schedule around them.

Why ipvs lost: maintainers, and a mismatched API

The immediate reason is maintenance. KEP-5495 states that sig-network lacks maintainers familiar with the ipvs backend code, and has been encouraging users who report ipvs bugs to move to the nftables backend where they can. The nftables backend, introduced by KEP-3866, was meant from the start to replace ipvs and iptables eventually.

The design reason is older. The docs page says ipvs mode did what it set out to do, better rule-synchronising performance and higher throughput than iptables, but the kernel IPVS API turned out to be a bad match for the Kubernetes Services API and the backend never implemented all the edge cases of Service functionality correctly. The release post adds that ipvs mode still uses iptables underneath. The 2025 post gives two concrete examples: the kube-ipvs0 device has to carry every Service IP address, and Services whose endpoints differ for local and remote clients were hard to express.

The feature people expect to miss is the choice of scheduler, from rr and lc to mh. The 2025 post argues it was not useful in practice: kube-proxy runs independently on every node, and the schedulers on each node had no way of sharing their state with the proxies on other nodes. KEP-5495 even plans a blog post about why IPVS schedulers are not actually useful in Kubernetes, which it says many ipvs users do not seem to realise. If least-connection balancing is why you chose ipvs, check that it is doing what you believe before you mourn it.

Audit first: which mode is each cluster really running

Before planning anything, find out what each cluster runs. The release post gives the first command below; the other two are mine, for distributions that do not use the kubeadm ConfigMap layout.

# 1. The command from the Kubernetes v1.37 release post. It assumes kube-proxy
#    reads its config from a ConfigMap in kube-system (the kubeadm layout).
kubectl -n kube-system get configmap kube-proxy \
  -o jsonpath='{.data.config\.conf}' | grep 'mode:'
#    An empty mode means the default, which is iptables in 1.37.

# 2. If your distribution passes flags instead of a ConfigMap, read the args.
kubectl -n kube-system get daemonset kube-proxy \
  -o jsonpath='{.spec.template.spec.containers[0].command}'
#    Look for --proxy-mode=ipvs or a --config file path.

# 3. Find ipvs-only tuning you will lose. The scheduler is the usual one.
kubectl -n kube-system get configmap kube-proxy \
  -o jsonpath='{.data.config\.conf}' | grep -A8 '^ipvs:'

Two readings need care. An empty mode field is not ipvs: the docs say an unset mode uses the recommended default, which is iptables in v1.37, and that a future version will change the default to nftables. They tell you to set the mode explicitly in every cluster so an upgrade cannot swap the backend under you. And on managed Kubernetes the provider usually owns this setting; I did not research any provider, so check its documentation before assuming you can edit it.

ipvs against nftables, on the points that decide a migration

The docs page calls nftables mode essentially a replacement for both the iptables and ipvs modes and recommends it as the replacement for ipvs. The table sets the two backends side by side on the differences that change what you operate.

Concernipvs modenftables mode
Kernel interfaceKernel IPVS and iptables APIsnftables API; needs Linux kernel 5.13 or later
Rule modelHash table in kernel space, with iptables rules still underneathOne rule dispatches through a verdict map; an update touches only the Services and endpoints that changed
Backend selectionipvs.scheduler: rr, wrr, lc, wlc, lblc, lblcr, sh, dh, sed, nq, mhSelects a backend Pod at random by default
NodePort addresses by defaultall local addressesprimary, the node's primary IPv4 and/or IPv6 addresses
Upstream attentionsig-network lacks maintainers familiar with the code; bug reporters are pointed at nftablesWhere fixes go; KEP-5495 lists backporting nftables bugfixes to v1.34 and v1.33 as a stage 1 criterion
Performance claimFaster rule sync and higher throughput than iptables, per the docs pageDocs: better than either. 2025 post: the same as ipvs, actually slightly better
Footprint on the nodeThe kube-ipvs0 device carries every Service IP addressIts own private nftables table, so no global lock contention with other components

The performance row is the one to read carefully. The 2025 post shows graphs I will not reproduce, and neither source gives a single number I can quote, so the defensible claim is no regression, not a speed-up. Measure on your own cluster. The docs add one more fact for old fleets: if the nodes are too old to run nftables mode, consider iptables mode rather than ipvs, because iptables performance has improved greatly since ipvs was introduced.

Preflight: kernel, host nft binary and neighbouring components

Three things can stop a cutover before it starts: the kernel, the nft binary on the host, and other software that touches netfilter. Run the checks below on every node pool, not on one representative node.

# Run on every node pool before you touch kube-proxy.

# Kernel: nftables mode needs 5.13 or later.
uname -r

# Host nft binary: the 2025 kube-proxy post says not to run nftables mode on
# nodes whose host filesystem has an nft older than 1.0.0, because early
# versions of the tool can interfere with other nftables users on the machine.
# No nft installed is fine; an old one is the problem.
nft --version

# Whole fleet at a glance (kubectl reports the kernel per node).
kubectl get nodes -o custom-columns='NODE:.metadata.name,KERNEL:.status.nodeInfo.kernelVersion'

# Who else writes netfilter rules? A CNI or NetworkPolicy engine that edits
# kube-proxy's iptables chains directly is the case to read the docs for.
kubectl -n kube-system get pods -o wide | grep -i -E 'calico|cilium|flannel|weave|antrea'

The kernel is less of a wall than it looks. KEP-5495 says all kernels too old to support nftables mode will be out of LTS by the end of 2026, yet it still lists old kernels as the main drawback of the whole plan. On neighbours, the 2025 post says most networking components will not care as long as they do not try to interact with or override kube-proxy's iptables rules, but monitoring tools that have not been updated may report less data in nftables mode. Read your CNI and NetworkPolicy documentation for an explicit statement.

The cutover: one config edit and a restart

The edit is small. The 2025 post says an existing cluster converts by updating the kube-proxy configuration and restarting the kube-proxy pods, with no node reboot, because kube-proxy deletes the existing iptables or ipvs rules when it starts in nftables mode.

# kube-proxy config.conf after the change. Field names are from the
# KubeProxyConfiguration v1alpha1 reference.
apiVersion: kubeproxy.config.k8s.io/v1alpha1
kind: KubeProxyConfiguration

# Always set the mode explicitly. The docs warn that the default will change
# from iptables to nftables in a future release, and that you should pin it
# so an upgrade cannot change your proxy backend under you.
mode: nftables

# nftables mode defaults to "primary" (the node's primary IPs). iptables and
# ipvs defaulted to "all". Leave it as primary only if nothing relies on a
# NodePort answering on every local address.
nodePortAddresses:
  - primary

# ipvs: block deleted on purpose. A leftover scheduler setting does nothing
# useful here, and the KEP suggests keeping the old ipvs flags as warned
# no-ops so a forgotten one does not hide.
# Apply and roll. No node reboot is needed: per the 2025 post, kube-proxy
# deletes the old iptables or ipvs rules when it starts in nftables mode.
kubectl -n kube-system edit configmap kube-proxy
kubectl -n kube-system rollout restart daemonset kube-proxy
kubectl -n kube-system rollout status daemonset kube-proxy

# On a node: the proxy now owns an nftables table named kube-proxy, and a
# single verdict map dispatches Service traffic.
nft list table ip kube-proxy | head -40

# Rollback is the same edit in reverse: set mode back and restart. Reverting
# makes kube-proxy delete its nftables rules, per the same post.

The risk is not the edit but where you apply it. Restarting the DaemonSet restarts every node's proxy at once, so do it on a staging cluster first and soak it before production. Staging a single node pool with a second DaemonSet is possible in principle; I have not tested it and the docs do not describe it, so I do not show it. Rollback is the same edit reversed, which is the real reason to keep the old config in version control.

Delete the ipvs block from the config after the cutover has soaked, not in the same change. KEP-5495 says it should probably keep the IPVS flags and options as no-ops with warnings, so a forgotten one does not break kube-proxy, which means a leftover is clutter rather than a hazard.

What breaks: four named differences and one without a switch

These come from the docs section on migrating from iptables mode to nftables, checked against the configuration reference. ipvs mode shares iptables mode's default of all addresses for NodePorts, so the first difference applies to you as well.

  • NodePort addresses narrow. The nftables default is primary, so a NodePort answers only on the node's primary IPv4 and/or IPv6 address. A probe or script using a secondary address goes quiet. Set nodePortAddresses explicitly; the docs give 0.0.0.0/0 as an example for all local IPv4 addresses.
  • Localhost NodePorts need an opt-in. They did not work in nftables mode originally. v1.37 adds an opt-in userspace proxy, alpha and behind the KubeProxyNFTablesLocalhostNodePorts feature gate, enabled by listing localhost in nodePortAddresses. The configuration reference says that in nftables mode this serves TCP only. The release post's example is a local container registry reached at localhost with a NodePort.
  • Host firewalls are your job. iptables mode adds rules accepting inbound NodePort traffic in case a firewall would block it. nftables mode does nothing there, so a local firewall must allow the whole NodePort range itself.
  • The conntrack workaround is gone. Kernels before 6.1 have a bug that can close long-lived TCP connections to Service IPs with the error Connection reset by peer. iptables mode installs a workaround that later caused other problems; nftables mode installs none by default. If the counter moves, run kube-proxy with conntrack-tcp-be-liberal.
# Ask kube-proxy whether anything depends on the behaviours nftables mode
# changes. The docs name these two counters in the iptables-to-nftables
# section. Non-zero means yes. I could not confirm that ipvs mode exports them,
# so an empty result on an ipvs node proves nothing.
curl -s 127.0.0.1:10249/metrics | grep -E \
  'iptables_localhost_nodeports_accepted_packets_total|iptables_ct_state_invalid_dropped_packets_total'

# Wrong: assume a localhost NodePort keeps working because "it is just a mode".
# Right: only if you opt in (alpha in 1.37, TCP only, loopback listed explicitly).
#   featureGates:
#     KubeProxyNFTablesLocalhostNodePorts: true
#   nodePortAddresses: ["primary", "localhost"]

# Old-kernel workaround for dropped long-lived TCP connections ("Connection
# reset by peer", kernels before 6.1). Only if the conntrack counter above moved:
#   kube-proxy --conntrack-tcp-be-liberal

The fifth difference has no switch: nftables mode picks backends at random. Anyone who set sh or mh for stickiness needs to move that requirement somewhere else before the cutover. The sources do not name a one-to-one replacement and I will not invent one.

The two counters in the snippet are named in the docs for the iptables-to-nftables move. I could not confirm that kube-proxy exports them while in ipvs mode, so an empty result on an ipvs node is not evidence that nothing depends on the behaviour. Test the NodePort and firewall cases directly on a staging cluster running nftables.

What to do on Monday

Six steps, in order. The first three cost nothing and remove the surprise even if you never migrate this quarter.

  1. Run the audit commands on every cluster and record the mode and the kube-proxy minor version in one place.
  2. Pin the mode explicitly in every kube-proxy config, iptables clusters included, because the docs say the default will change to nftables in a future release.
  3. List every node's kernel and host nft version, and mark the pools below kernel 5.13 or with nft older than 1.0.0.
  4. On staging, switch to nftables mode and test each NodePort on every address you actually use, any localhost NodePort, and your host firewall rules.
  5. For pools that cannot run nftables mode, plan iptables mode rather than staying on ipvs, as the docs advise.
  6. Put production on the calendar before your upgrade to v1.40. After that gate flip, a cluster still in ipvs mode without the override will have kube-proxy exit with an error.

This post reflects the documents as I read them on 17 October 2026. The version numbers after v1.37 are plans, so check the virtual-IPs page for your release before acting on a date.

The rule to carry: a deprecated kube-proxy mode is a migration with a deadline measured in minor releases, and the work is mostly not the config line but the four behaviours around it. Pin the mode, check the kernel, change one cluster, and let the NodePort and firewall tests tell you what you depended on.

FAQ

Frequently Asked Questions

Short answers to what readers ask most about this topic.

Is kube-proxy ipvs mode being removed from Kubernetes?

Yes. The Kubernetes docs mark ipvs mode as deprecated since v1.35, disabled by default from v1.40 behind the KubeProxyIPVS feature gate, and fully removed in v1.43. KEP-5495 adds that the gate itself is removed in v1.46.

What should I replace kube-proxy ipvs mode with?

The docs recommend nftables mode as the replacement, calling it essentially a replacement for both iptables and ipvs. It needs Linux kernel 5.13 or later. If your nodes are too old for it, the docs suggest iptables mode rather than staying on ipvs.

How do I check which kube-proxy mode my cluster uses?

The v1.37 release post gives this command: kubectl -n kube-system get configmap kube-proxy with a jsonpath of the config.conf key, piped to grep mode. An empty mode means the default, which is iptables in v1.37. Clusters that pass flags instead of a ConfigMap need the DaemonSet args checked.

Do I need to reboot nodes to switch kube-proxy to nftables?

No. According to the 2025 Kubernetes post, you update the kube-proxy configuration and restart the kube-proxy pods. When kube-proxy starts in nftables mode it deletes the existing iptables or ipvs rules, and reverting deletes the nftables rules in turn.

What stops working after moving from ipvs to nftables mode?

NodePorts answer only on the node's primary addresses by default, localhost NodePorts need an alpha opt-in in v1.37, host firewalls must allow the NodePort range themselves, and the conntrack workaround is not installed. IPVS schedulers also disappear, since nftables mode picks backends at random.