kube-proxy Modes in Kubernetes: iptables vs IPVS vs eBPF
How a Service's stable address turns into real traffic reaching a real Pod, and how three different engines inside the Linux kernel do that job with very different costs, speeds and trade-offs.
Think of a large company with a single customer helpline number. You dial it, and a call centre quietly connects you to whichever agent is free. Agents join and leave all day, but the number never changes. Behind the scenes there is a switchboard that keeps an up-to-date list of agents and decides where each call goes. The caller never sees the switchboard. In Kubernetes the helpline number is a Service address, the agents are Pods, and the switchboard is the machinery that kube-proxy sets up on every node.
There is more than one way to build that switchboard. One uses a long list of packet-filtering rules, another uses a purpose-built load-balancing table, a third uses small programs that run directly inside the kernel. Each has strengths and weaknesses that only show up at scale, under failure or during debugging. This post explains all of them from the beginning. You do not need networking expertise. Every term is defined, compared with something from daily life, and then used in the next section.
1The Problem: Pods Come and Go, Clients Need a Stable Address
Start with why Kubernetes needs a switchboard at all.
Pods are temporary
A Pod is the smallest unit that runs your containers, and every Pod gets its own IP address. But Pods are disposable. They are replaced during updates, rescheduled when a node fails, and created or removed as load changes. Each replacement gets a brand new IP address. If a front-end program stored the address of a back-end Pod, it would break the next time that Pod was replaced, which in a busy cluster could be minutes later.
Imagine writing a friend’s temporary hotel room number in your address book. Next week they move to a different hotel, and the number is useless. What you really need is a permanent contact, such as a phone number that follows them wherever they go. A Service is that permanent contact for a group of Pods.
Many Pods behind one name
Usually a Service fronts several identical Pods, so the same request can be answered by any of them. That means somebody must choose which Pod receives each new connection. This is load balancing. The choice must be fast, because it happens for every connection, and it must follow changes quickly, because Pods appear and disappear all the time.
Where the choice can be made
In the client program
Every application would need to know how to look up and balance across Pods. That means every language and every team repeats the same logic.
In a central load balancer
All traffic crosses one extra hop and one extra bottleneck, which is slower and a single point of failure.
On every node, in the kernel
Each node rewrites destinations locally, so there is no extra hop and no central bottleneck. This is the Kubernetes approach, and kube-proxy is the program that sets it up.
Many beginners picture kube-proxy as a program that every packet flows through, like a traditional proxy server. In modern Kubernetes it usually does not touch your traffic at all. It only writes instructions into the kernel, and the kernel moves the packets. The rest of this post builds on that distinction.
2Services and Endpoints: A Quick Refresher
The vocabulary you need before looking inside the switchboard.
What a Service is
A Service is a Kubernetes object that gives a group of Pods a stable name and a stable virtual IP address. It picks its Pods using labels. The virtual address is called the ClusterIP. It is virtual because it is not attached to any real network card. No machine answers at that address. It exists only as a rule in the kernel’s packet handling.
Endpoint slices
The list of real Pod addresses behind a Service is stored in objects called EndpointSlices. A controller keeps them up to date as Pods become ready, fail or disappear. Large Services are split across several slices, which keeps each object small and makes updates cheap. kube-proxy reads these slices to know where traffic may be sent.
ClusterIP
The default. Reachable only from inside the cluster, through the virtual address.
NodePort
Opens the same port on every node, so outside clients can reach the Service by connecting to any node.
LoadBalancer
Asks the cloud provider for an external load balancer that forwards into the NodePorts.
Headless
No virtual address at all. DNS returns Pod addresses directly, so kube-proxy does nothing for it.
A Service is the company’s published helpline number. EndpointSlices are the staff roster showing which agents are currently on shift. kube-proxy is the technician who keeps each office’s phone system updated whenever the roster changes.
What DNS adds
People rarely type the virtual address. Cluster DNS gives each Service a name, so a program can connect to a friendly name that resolves to the ClusterIP. DNS itself is handled by a separate component and has nothing to do with kube-proxy modes. DNS answers “what is the address of this Service,” and kube-proxy’s work starts when a packet is sent to that address.
Keep two questions apart: “which address does this name mean?” is answered by DNS, and “which Pod receives this connection?” is answered by the kube-proxy machinery. Confusing them is a common source of debugging detours.
3What kube-proxy Does, and What It Does Not
A small program with one job: translate cluster objects into kernel instructions.
The job in one sentence
kube-proxy runs on every node, watches Services and EndpointSlices through the API server, and programs the node’s kernel so that traffic sent to a Service address gets redirected to one of the healthy Pods behind it. It is a translator between the Kubernetes world of objects and the Linux world of network rules.
Watch
It keeps an up-to-date local picture of Services and EndpointSlices using the same watch and informer machinery that other controllers use.
Compute
It works out what the node’s forwarding rules should look like for the current set of Services and ready Pods.
Program
It writes those rules into the kernel, in a way that depends on the chosen mode.
Repeat
Whenever something changes, and also periodically as a safety net, it repeats the cycle so the kernel never drifts far from reality.
What it does not do
It does not carry your packets
In the modes used today, packets are rewritten and forwarded entirely by the kernel. If kube-proxy crashes, existing rules stay in place and traffic keeps flowing with the last known state, though new changes will not be applied until it returns.
It does not enforce network policies
Rules about which Pods may talk to which Pods are the job of the cluster network plugin, not kube-proxy. People often blame kube-proxy for blocked traffic that a policy actually caused.
It does not provide DNS or service discovery names
Names come from cluster DNS. kube-proxy only handles addresses.
It does not do application-level routing
Decisions based on web paths, headers or cookies belong to ingress controllers and service meshes. kube-proxy works at the connection level and knows nothing about the contents of your requests.
A short history
The earliest design really did proxy traffic through a user-space program, which was slow because every packet had to cross between kernel and program. It was replaced by the rule-based approach, first with iptables, then with IPVS as an alternative, and more recently with nftables as a modern successor. The old user-space mode has been removed from current releases. Separately, some network plugins now take over the whole job using eBPF and no longer run kube-proxy at all. These are the engines we compare in this post.
The mode is a setting in kube-proxy’s configuration. Available modes and defaults depend on the operating system and the Kubernetes version, so always read the release notes of your version before choosing. On Linux the default has long been iptables.
4The Control Loop: From Object to Kernel Rule
Whatever the mode, the flow from a Kubernetes change to a kernel change looks the same.
Following one change
Suppose a new Pod for a Service becomes ready. The endpoint controller adds its address to an EndpointSlice. kube-proxy on every node has a watch open on EndpointSlices, so each instance receives the change within moments. It updates its internal picture, notes that the Service’s rules are now out of date, and schedules a rule update. Updates are deliberately batched, so a burst of changes leads to one rule sync rather than hundreds.
sequenceDiagram
participant P as New Pod
participant E as Endpoint controller
participant A as API Server
participant K as kube-proxy on each node
participant N as Node kernel
P->>A: Pod becomes ready
E->>A: Add Pod address to EndpointSlice
A-->>K: Watch event: EndpointSlice changed
K->>K: Update local picture and mark Service dirty
K->>K: Wait for batching interval
K->>N: Program updated rules
Note over N: New connections can now reach the new Pod
Batching and timing
kube-proxy has two timing ideas. One is a minimum gap between syncs, so that a storm of changes does not cause continuous reprogramming. The other is a periodic full sync, which re-applies the entire desired state even if no event arrived, repairing any damage done by something else. These intervals trade freshness against cost. Short gaps make new Pods reachable sooner, long gaps save CPU on busy clusters.
A receptionist updates the staff board once every few minutes with all the changes, instead of walking to the board after every single person who arrives. Once a day the receptionist also redoes the whole board from the master roster, just in case someone scribbled on it.
Readiness matters
Only Pods that report themselves as ready receive traffic. A Pod that is still starting, or that has failed its readiness check, is left out of the rules. This is why readiness checks are so important to a smooth rollout: they decide when the switchboard starts sending calls to a new agent. Pods that are shutting down are treated specially in newer versions, so existing connections can finish while no new connections arrive.
In a healthy cluster, the delay between a Pod becoming ready and every node being able to reach it is usually a second or two, though it can be longer on very large clusters with heavy rule sets. This delay, often called network programming latency, is one of the most useful numbers to watch.
5iptables Mode: Load Balancing with Packet Rules
The long-standing default. It works almost everywhere and is simple to reason about, up to a point.
What iptables is
iptables is a Linux tool for managing the kernel’s packet-filtering rules. Rules are organised into tables and chains, and each packet walks through the chains and is matched against rules in order. A rule can accept a packet, drop it, or change its destination. Changing the destination is called destination NAT, and it is the trick that makes Services work: a packet addressed to the virtual IP has its destination rewritten to a real Pod address.
Imagine a mail sorting room where each letter passes a long row of clerks. The first clerk checks if it is for the helpline, the next clerk checks another address, and so on. When a clerk finds a match, they cross out the address and write a real person’s address instead. The longer the row of clerks, the longer each letter takes to sort.
How the rules are organised
kube-proxy creates a hierarchy of custom chains. A top-level chain holds one entry per Service address and port. Each of those jumps to a per-Service chain, and the per-Service chain holds one entry per Pod behind it. The per-Pod entry performs the actual address rewriting. To spread load, the per-Service chain uses a random-probability rule: the first Pod gets a chosen probability, the next one a larger share of what is left, and the last one takes the remainder, which produces an even random split.
flowchart TD
P["Packet to Service IP and port"] --> T["Top-level services chain"]
T --> M{"Matches a Service entry?"}
M -- "No" --> X["Continue normal handling"]
M -- "Yes" --> S["Per-Service chain"]
S --> R{"Random pick by probability"}
R --> E1["Per-Pod rule 1: rewrite to Pod 1"]
R --> E2["Per-Pod rule 2: rewrite to Pod 2"]
R --> E3["Per-Pod rule 3: rewrite to Pod 3"]
The return path and connection memory
Once a connection’s first packet has been rewritten, the kernel’s connection tracking table remembers the choice. Every later packet of that connection follows the same mapping without walking the chains again, and reply packets are automatically rewritten back so the client believes it is talking to the Service address. This memory is why changing the rules does not break connections that are already open.
Advantages
- Available on practically every Linux system with no extra kernel modules to load
- Long track record and very widely understood by operators
- Easy to inspect with familiar tools
- No special dependencies, which makes it a safe default
- Good enough for the majority of small and medium clusters
Disadvantages / Trade-offs
- Rule matching grows with the number of Services and Pods, so very large clusters slow down
- Updating rules means rewriting large rule sets, which costs CPU and can briefly lock the table
- Load balancing is random only, with no smarter strategies
- Debugging thousands of auto-generated rules is tiring
- Rule churn in busy clusters can delay how quickly new Pods become reachable
The trouble usually shows up in clusters with thousands of Services and many tens of thousands of Pods. Matching a packet against a long chain, and rewriting a very large rule set on every change, become noticeable costs. Exact thresholds depend on hardware, kernel and kube-proxy version, so measure your own cluster rather than trusting rules of thumb.
6IPVS Mode: A Purpose-Built Load Balancer in the Kernel
Instead of a long list of rules, use the kernel’s own virtual server feature, built for exactly this job.
What IPVS is
IPVS, short for IP Virtual Server, is a load-balancing feature that lives inside the Linux kernel. It was designed for large-scale load balancing long before Kubernetes existed. It keeps a table of virtual services, each with a list of real servers behind it, and looks them up using hash tables, so finding the right virtual service takes about the same time whether there are ten Services or ten thousand.
Instead of a long row of clerks checking every letter one by one, imagine a single sorting machine with an index card for every address. The machine looks up the card instantly, finds the list of real recipients, and picks one using a chosen strategy. The size of the index barely changes how long a lookup takes.
How kube-proxy uses it
In IPVS mode, kube-proxy creates one virtual server for each Service address and port, and adds one real server for each ready Pod. The Service addresses are attached to a dummy network interface on the node, so the kernel treats them as local addresses and hands matching packets to IPVS. A small number of packet-filter rules and address sets still exist alongside IPVS to handle tasks such as marking packets that need address masquerading. So IPVS mode is not entirely rule-free, but the number of rules stays small and mostly constant regardless of how many Services exist.
flowchart TD
P["Packet to Service IP"] --> D["Service IP is local on a dummy interface"]
D --> V["IPVS virtual server table lookup by hash"]
V --> SCH{"Scheduling algorithm"}
SCH --> R1["Real server: Pod 1"]
SCH --> R2["Real server: Pod 2"]
SCH --> R3["Real server: Pod 3"]
Choice of scheduling algorithm
One real advantage of IPVS is that it offers several ways to choose the real server, not just a random pick. The choice is configurable in kube-proxy.
Round robin
Takes turns in order. Simple, predictable and the usual default.
Least connection
Sends new connections to the Pod currently holding the fewest active ones, helpful when connection lengths vary a lot.
Source hashing
Maps a given client address to the same Pod each time, giving a basic form of stickiness without cookies.
Destination hashing and others
Other variants exist, such as shortest expected delay and never queue, for special traffic patterns.
Requirements and caveats
IPVS needs specific kernel modules loaded on every node. Missing modules are one of the commonest reasons for failures when switching modes, and some minimal or locked-down operating systems do not include them. IPVS also keeps its own connection table with its own timeouts, separate from the general connection tracking, which means that some long-idle connections may be dropped by timeout settings that differ from what teams are used to. Tuning these timeouts is a known part of running IPVS well.
Advantages
- Constant-time lookups, so large numbers of Services barely slow packet handling
- Incremental updates change only what is needed, not an entire rule set
- Several load-balancing algorithms to choose from
- Mature kernel feature with a long history in load-balancer products
Disadvantages / Trade-offs
- Needs extra kernel modules and still uses some packet-filter rules and address sets
- Its own connection timeouts can surprise teams with long-lived idle connections
- Fewer people know how to debug it compared with iptables
- The project has signalled a move away from this mode in recent releases, so check the notes for your version before building new plans around it
Because the future of IPVS mode in kube-proxy is under discussion, treat it as a strong option for existing large clusters rather than an automatic choice for new designs. Read the current release notes and deprecation notices for your Kubernetes version.
7nftables Mode: The Modern Successor to iptables
A newer in-kernel option that keeps the rule-based idea but fixes its biggest scaling weakness.
What nftables is
nftables is the modern replacement for iptables in the Linux kernel. It offers a cleaner way to describe rules and, crucially, supports sets and maps: data structures that let one rule say “if the destination is in this set, jump to the matching target found in this map.” Looking up an entry in a map takes about the same time however large the map is.
iptables is a long checklist read from the top. nftables is a phone book: you flip straight to the right name. The checklist gets slower with every added line. The phone book stays about as fast however many names it holds.
What changes for kube-proxy
In nftables mode, kube-proxy keeps its familiar job but expresses Services as map entries instead of long chains. Updates are applied as atomic transactions, so the kernel switches from the old rules to the new ones in one step, with no window in which rules are half-written. This gives performance in large clusters that is much closer to IPVS, without IPVS’s extra kernel modules and its separate connection table. The mode matured through the recent releases and is meant as the long-term in-kernel path on Linux, so check your version’s notes for its current status.
| Aspect | iptables | nftables |
|---|---|---|
| Rule lookup for Services | Walks chains, cost grows with Services | Map lookup, cost stays nearly flat |
| Rule update style | Rewrites large rule sets | Atomic incremental transaction |
| Kernel requirement | Almost any | A reasonably modern kernel with nftables support |
| Operator familiarity | Very high | Growing, tools differ |
| Interaction with other tools | Many tools write iptables rules | Some tools still assume iptables, so check for conflicts |
Switching to nftables mode affects more than kube-proxy. Other software on the node that edits iptables rules, such as some security agents or network plugins, may not expect it. Test the combination in a non-production cluster, and confirm that your operating system’s kernel and tools support it.
Why mention it in an iptables-versus-IPVS-versus-eBPF post
Because for many teams in the coming years the real question is no longer “iptables or IPVS,” but “stay with kube-proxy in its newer nftables form, or move to an eBPF-based network plugin that replaces kube-proxy.” Knowing that nftables exists keeps the comparison honest and up to date.
8eBPF: Replacing kube-proxy Altogether
The third approach is different in kind. It does not tune kube-proxy, it retires it.
What eBPF is
eBPF is a technology that lets small, safe programs run inside the Linux kernel at specific hook points, such as when a packet arrives or when a program opens a connection. The kernel checks each program for safety before running it, so it cannot crash the system. Network plugins use eBPF to handle packets with custom logic, without the long rule chains of iptables.
Instead of giving the mail room a rulebook to read for every letter, you place a small smart sorting gadget directly at the door of the building. The gadget has the address lookup built in, acts the instant a letter arrives, and was inspected for safety before it was allowed to be installed.
Not a kube-proxy mode, a replacement
This point is important. eBPF is not one of the values you can set in kube-proxy’s mode field. Instead, a network plugin that uses eBPF can take over Service handling completely. Popular plugins offer a kube-proxy replacement feature, and with it enabled you do not run kube-proxy at all. The plugin watches the same Services and EndpointSlices, but keeps its own eBPF tables, usually hash maps, and the eBPF programs consult those maps.
Where the load balancing happens
At connection time, inside the client
For traffic from Pods and processes on the node itself, an eBPF program can attach to the moment a program calls connect. It swaps the Service address for a real Pod address right there, before any packet is created. The packets that follow already carry the Pod address, so there is no per-packet rewriting and no connection-tracking entry for the translation. Replies need no reverse translation either.
At packet time, for outside traffic
For traffic arriving from outside the cluster, such as NodePort or load balancer traffic, programs attached at the network card or just above it can pick a Pod and forward the packet. Some plugins can do this very early, before the normal network stack spends effort on the packet, and can use consistent hashing so that different nodes make the same choice for the same connection.
Direct server return
Some plugins support a mode where the reply goes straight from the chosen Pod back to the client without passing through the node that received the request, which saves a hop for large responses.
flowchart LR
subgraph IN["Traffic from inside the node"]
C["Pod calls connect to Service IP"] --> H1["eBPF hook at connect"]
H1 --> M1[("eBPF service map")]
M1 --> T1["Destination swapped to a real Pod IP"]
end
subgraph OUT["Traffic from outside the cluster"]
X["Packet arrives at node port"] --> H2["eBPF hook at network device"]
H2 --> M2[("eBPF service map")]
M2 --> T2["Packet forwarded to chosen Pod"]
end
Advantages
- Constant-time lookups using hash maps, so very large clusters stay fast
- Connect-time translation removes per-packet work for in-cluster traffic
- Preserves the original client address more easily for external traffic
- Incremental map updates with no big rule rewrites
- Rich built-in observability from the same eBPF layer
Disadvantages / Trade-offs
- Requires a reasonably modern kernel with the needed eBPF features
- Ties Service handling to one network plugin, which raises switching costs
- Familiar tools such as iptables listings no longer show the whole story
- Debugging needs different skills and tools
- Behaviour with very old applications or special setups may need testing
More than one popular network plugin offers an eBPF data plane with kube-proxy replacement. Features, requirements and maturity differ between plugins and between versions, so read the specific plugin’s documentation for your kernel and cluster version.
9The Journey of a Packet in Each Engine
Put the three approaches side by side by following the same request through each.
The scenario
A front-end Pod on node A opens a connection to a back-end Service. The Service has three ready Pods, one on node A, one on node B and one on node C. We follow the first packet and what happens next, in each engine.
iptables mode
The packet leaves the front-end Pod and enters the node’s packet-handling path. It is matched against the top-level service chain, jumps to the Service’s chain, a random pick selects one Pod rule, and the destination is rewritten. The connection tracker records the mapping, so later packets skip the chains.
IPVS mode
The packet is addressed to a Service IP that the node considers local. The kernel hands it to IPVS, which looks up the virtual server by hash, applies the chosen scheduling algorithm, picks a real server, and forwards the packet with the destination rewritten. IPVS records the connection in its own table.
nftables mode
The packet is matched by a single lookup into a map keyed by Service address and port. The map’s result points to a Pod-selection step, the destination is rewritten, and the connection tracker records it, much like iptables but without the long chain walk.
eBPF replacement
When the front-end program calls connect, a hook consults the service map and writes a real Pod address as the destination before the first packet exists. The kernel simply sends packets to that Pod, and nothing later needs rewriting.
sequenceDiagram
participant F as Front-end Pod on node A
participant K as Node A kernel
participant B as Back-end Pod on node B
Note over F,K: iptables, IPVS or nftables
F->>K: Packet to Service IP
K->>K: Match Service, pick Pod, rewrite destination
K->>B: Packet to Pod IP on node B
B-->>K: Reply from Pod IP
K->>K: Reverse rewrite to Service IP
K-->>F: Reply appears to come from Service IP
Note over F,K: eBPF connect-time translation
F->>K: connect to Service IP
K->>K: Hook swaps in Pod IP before sending
F->>B: Packets go straight to Pod IP
B-->>F: Replies need no reverse rewrite
What the comparison teaches
All approaches pick a Pod and rewrite a destination. The difference is when and how. The rule-based engines do it as the packet passes through, and rely on connection tracking to remember the choice and to undo the rewrite on replies. The connect-time eBPF approach does it once at the start for in-node clients, so the rest of the connection is plain. For traffic that arrives from outside the node, all engines must rewrite as the packet passes, so the advantage of connect-time translation applies mainly to in-cluster, same-node origins.
10Side-by-Side Comparison
One table that collects the differences that matter in day-to-day decisions.
| Aspect | iptables | IPVS | nftables | eBPF replacement |
|---|---|---|---|---|
| Where it lives | kube-proxy mode | kube-proxy mode | kube-proxy mode | Network plugin, no kube-proxy |
| Service lookup cost | Grows with Service count | Near constant, hash table | Near constant, map lookup | Near constant, hash map |
| Rule or table updates | Heavy, rewrites large sets | Incremental | Atomic and incremental | Incremental map updates |
| Balancing choices | Random | Round robin, least connection, hashing and more | Random style | Plugin dependent, often hash-based and consistent |
| Kernel needs | Minimal | IPVS modules | nftables support | Modern kernel with eBPF features |
| Debugging style | Familiar, but long rule lists | IPVS tools plus some rules | nftables tools | eBPF tools and plugin tooling |
| Fit for small clusters | Excellent | Fine, little gain | Fine | Fine, adds dependency |
| Fit for very large clusters | Strained | Strong | Strong | Strong |
| Main risk | Slowdown with scale | Timeout surprises and uncertain future | Tool interactions on the node | Plugin lock-in and kernel needs |
Published speed comparisons depend heavily on the test hardware, kernel version, number of Services and Pods, and traffic pattern. A result that shows a huge gap in a test with tens of thousands of Services may show almost nothing in a cluster with a few hundred. Always benchmark with your own workload shape.
Two kinds of cost
It helps to separate two costs when comparing. The data path cost is how long it takes to handle each packet or connection. The control path cost is how long and how much CPU it takes to apply a change. A cluster with few, long-lived connections but frequent Pod changes cares mostly about the control path. A cluster with stable Pods but millions of short connections cares more about the data path. The engines differ in both, so identify which one hurts you before choosing.
11Performance and Scalability in Practice
Where each engine’s costs show up, and what you can do before changing the engine.
Where problems appear first
In rule-based modes, the first visible symptom is usually slow rule programming rather than slow packets. As the rule set grows, each sync takes longer, and during busy periods syncs queue up. The effect is that new Pods take longer to become reachable and removed Pods keep receiving traffic a little longer. CPU use of kube-proxy on every node climbs, and in extreme cases the table update itself becomes a source of short pauses for traffic.
Tuning before switching
Adjust sync timing
Lengthening the minimum interval between syncs reduces CPU spent on rule rewriting, at the cost of slightly slower reaction to changes.
Keep endpoints lean
Services with huge numbers of Pods multiply rule counts. Splitting very large Services, or using topology hints, reduces the work per node.
Avoid needless Service churn
Applications that create and delete Services constantly force constant rule updates. Reuse Services where you can.
Tune connection tracking
A full or too-small connection table causes dropped connections and strange timeouts, so size it for your traffic.
Give kube-proxy enough resources
Starved kube-proxy Pods sync slowly. Set sensible CPU and memory requests for the node agent.
Locality: keeping traffic close
Performance is not only about CPU. Sending a connection to a Pod in another zone adds latency and, on many clouds, cross-zone transfer charges. Newer Kubernetes versions let a Service prefer endpoints in the same zone, or on the same node, and kube-proxy modes honour those hints when programming rules. This can cut both latency and cost, but it must be used with enough Pods in each zone, otherwise a few Pods get overloaded while others sit idle.
Preferring the same zone
With zone-aware hints, each node’s rules favour Pods in its own zone when there are enough of them. If a zone has too few Pods, the hints are dropped, so that traffic is not concentrated on a tiny set of Pods.
Preferring the same node
For per-node agents, the Service can prefer a Pod on the very same node, which removes network hops entirely, with a fallback if no local Pod exists.
Switching engines is a significant operation. If kube-proxy’s sync time and CPU are modest, and Pods become reachable quickly, the existing mode is probably fine, and the effort is better spent elsewhere. Let measurements, not fashion, drive the decision.
12Traffic Policies, Source IPs and Session Stickiness
The options that change which Pod is chosen and what address the Pod sees.
External traffic policy
For Services reachable from outside, such as NodePort and LoadBalancer types, there is a setting that controls how a node treats incoming traffic. In the Cluster setting, a node may forward the connection to a Pod on any other node, spreading load evenly but adding a hop and usually masking the original client address. In the Local setting, a node only sends traffic to Pods running on itself, which keeps the client address intact and avoids an extra hop, but can create uneven load if Pods are not spread evenly across nodes.
| Setting | Extra hop? | Client address kept? | Load evenness | Needs |
|---|---|---|---|---|
| Cluster | Possible | Usually hidden by address masquerading | Even across all Pods | Nothing special |
| Local | No | Yes | Depends on Pod spread per node | Health checks so load balancers skip nodes without local Pods |
Cluster policy is like a reception desk that may pass a visitor to any department in the building, which balances work but means the visitor’s own badge details get replaced by the desk’s. Local policy only lets the desk use staff sitting at that desk, so details stay accurate but a desk with no staff cannot help.
Why the source address matters
Applications sometimes need the real client address for logging, rate limiting or access rules. Address masquerading replaces it with the node’s own address, which hides the client. The Local policy, or an eBPF data plane that avoids masquerading, keeps the original address. This is one practical reason teams move to eBPF or adopt the Local policy for externally exposed Services.
Session affinity
A Service can ask that connections from the same client address keep going to the same Pod for a period of time. All modes support this, but they implement it differently, and it is based only on the client’s network address, not on cookies. Clients behind a shared address, such as a large office network or another proxy, will all stick to one Pod, which can overload it.
Health checks for load balancers
When the Local policy is used with a cloud load balancer, kube-proxy exposes a small health-check port on each node. It answers positively only when the node has a ready local Pod for the Service. The cloud load balancer uses that answer to stop sending traffic to nodes that cannot serve it. Without this, the Local policy would drop connections on nodes without Pods.
If one node has three Pods and another has one, the node with three Pods does not receive three times the traffic under common cloud load balancer behaviour. Each node typically gets a similar share, so the lone Pod can be overloaded. Spread Pods evenly with topology rules when you use the Local policy.
13Connection Tracking and Graceful Termination
What happens to connections that are already open when a Pod goes away, and why some mistakes only appear with UDP.
The connection tracking table
The kernel keeps a table of active connections, called the conntrack table. In the rule-based modes it holds the mapping from a Service address to the chosen Pod, so replies can be rewritten back. The table has a fixed size. If it fills up, new connections are dropped, which shows up as mysterious timeouts on busy nodes. Monitoring its usage is as important as monitoring CPU.
Stale entries and UDP
When a Pod behind a Service is removed, entries pointing to it may remain in the table. For TCP, the connection simply breaks and the client reconnects, which picks a live Pod. For UDP, there is no connection to break, so the client’s packets may keep going to the removed Pod’s address until the entry expires, producing a silent black hole. For this reason kube-proxy actively clears stale entries for UDP Services when endpoints change. DNS is a common UDP Service, which is why conntrack problems often show up as intermittent DNS failures.
The conntrack table is a notebook where the switchboard writes “caller 17 is talking to agent 4.” If agent 4 leaves, a caller already in conversation hears silence. A phone call ends and the caller redials. A one-way message such as UDP has no call to end, so messages keep being dropped into the empty chair until someone crosses out the notebook entry.
Graceful termination
When a Pod is told to stop, it should keep serving the connections it already has, while no new connections arrive. Modern Kubernetes marks such endpoints as terminating, and kube-proxy modes cooperate: they stop choosing the terminating Pod for new connections but leave existing mappings alone until the Pod is gone. In IPVS mode this is done by lowering the real server’s weight to zero, so it receives nothing new but keeps its current connections.
Pod receives a stop signal
It is marked as terminating, and its readiness becomes false.
Rules stop sending new connections to it
kube-proxy updates the rules once the watch event arrives. This takes a short moment, during which a few new connections may still arrive.
Existing connections finish
The Pod keeps serving them until they end or the grace period expires.
Pod is removed
Remaining entries are cleaned up, so no traffic is sent to a Pod that no longer exists.
Because rule updates take a moment, applications are best configured to keep serving for a few seconds after receiving a stop signal. A short pre-stop delay in the Pod prevents the tiny window where new connections still reach a Pod that is already closing.
14Real-World Choices: Who Uses What and Why
Practical guidance for different cluster shapes.
Small and medium clusters with ordinary traffic
The default iptables mode is usually the right answer. It is available everywhere, widely understood and fast enough when the number of Services is in the hundreds. Changing it brings extra risk with little visible benefit.
Large clusters with thousands of Services
Teams often move to IPVS or, in newer versions, nftables mode, or to an eBPF network plugin. The goal is to stop rule programming time from growing with the Service count and to keep new Pods reachable quickly.
Platforms that need strong observability and security from one layer
Organisations that already adopt an eBPF-based network plugin for visibility and policy often enable its kube-proxy replacement as well, consolidating Service handling, policy and flow visibility in one component.
Environments with very mixed traffic lengths
Where connection durations vary hugely, such as a mix of quick requests and long streaming sessions, IPVS with the least-connection algorithm can give a fairer spread than random choice.
Managed Kubernetes services
Managed platforms usually choose the engine for you, often a default mode or their own network plugin, and may limit what you can change. Check your provider’s documentation to see which modes are supported and whether the engine can be switched on existing node pools.
Windows nodes
Windows nodes use a different kube-proxy implementation tied to the Windows networking stack, so the Linux engines in this post do not apply there. Mixed clusters must consider both.
15Migrating Between Modes Safely
Changing the engine under live traffic is possible, but it rewards careful, gradual steps.
Why migration is delicate
The Service switchboard sits under every connection in the cluster. A mistake can break internal calls between applications, DNS or access from outside. Switching modes replaces one set of kernel structures with another, and leftover rules from the old mode may linger unless they are cleaned up. A staged approach limits the blast radius.
Confirm prerequisites
Check kernel version and modules, operating system support and compatibility of any other node software that edits network rules.
Rehearse in a non-production cluster
Reproduce your Service count, traffic pattern and failure cases, and measure sync time, latency and error rates before and after.
Pick a small canary node pool
Change the mode on a few nodes, usually by rolling them with a new configuration, and keep the rest unchanged. Mixed modes across nodes are generally workable because each node handles traffic leaving it.
Watch the right signals
Monitor error rates, connection resets, DNS success, programming latency and conntrack usage on canary nodes versus the rest.
Roll out gradually, node pool by node pool
Expand only after each stage looks healthy, and keep a clear rollback path to the previous mode.
Clean up old rules
Ensure leftover rules from the previous engine are removed, either by node replacement or by the supported cleanup procedure, so stale rules do not interfere.
Special care for eBPF replacement
Moving to an eBPF plugin that replaces kube-proxy is a larger step than switching modes, because it is often combined with changing or reconfiguring the network plugin itself. Plan it as a network migration project, with a tested rollback, a maintenance window for the riskiest stages, and a clear plan for removing kube-proxy only after the replacement has proven itself. Expect to retrain operators on new tools.
Having both kube-proxy and an eBPF replacement trying to handle Services on the same node leads to confusing, duplicated or conflicting behaviour. Each node should have exactly one owner of Service translation at any moment.
16Security Considerations
The component that rewrites every Service connection deserves careful protection.
Privileges of the node agent
kube-proxy edits kernel networking state, so it runs with high privileges on every node, usually with network administration rights. A compromised kube-proxy Pod could redirect traffic, which is powerful. Protect it by restricting who can change its configuration, using trusted images, and limiting its access to the API server to only what it needs. Its identity is normally bound to a narrow role for reading Services and EndpointSlices.
Exposure through NodePorts and external addresses
A NodePort Service opens a port on every node, often across a wide range, whether or not the node runs any of the Service’s Pods. This enlarges the attack surface. Limit where nodes are reachable from with network firewalls and security groups, and restrict which address ranges NodePorts bind to when you do not need them on every interface. For load balancer Services, use source range restrictions so that only expected networks can connect.
Service definitions can include arbitrary external addresses. If any user may set them, one tenant can attract traffic meant for another address inside the network. Restrict this capability with admission controls, unless there is a clear need for it.
Policy is a different layer
Because kube-proxy does not enforce network policy, a cluster without a policy-capable network plugin has no Pod-to-Pod restrictions at all, however carefully the Services are set up. Network policy and Service translation interact: policy is evaluated against real Pod addresses after translation in rule-based modes, while eBPF plugins implement both together and apply identity-aware rules. Test policies with your chosen engine, since subtle differences exist in how they treat Service traffic.
Information exposure
Metrics and health ports
kube-proxy exposes a metrics endpoint and a health endpoint. Bind them to local or internal interfaces and protect them, because they reveal details of cluster structure.
Rule visibility
Anyone with access to a node can read its rules and learn Service addresses and Pod placement. Limit node access and treat node shells as privileged.
Supply chain for plugins
An eBPF-based plugin loads programs into the kernel with high privilege. Use trusted, version-pinned releases from reputable sources, and review the permissions its Pods require.
17Monitoring, Logging and Troubleshooting
What to watch, and how to find the problem when a Service does not respond.
Metrics worth tracking
Sync duration
How long each rule synchronisation takes. A steady rise points to a growing rule set or an overloaded node.
Network programming latency
The delay from a change in cluster objects to the rules being active on nodes. This reflects what users feel during rollouts.
Last successful sync time
If this stops advancing, kube-proxy is stuck or failing, and rules on that node are going stale.
Rule and entry counts
Total rules or table entries, to see how the data structure grows with the cluster.
Conntrack usage
How full the connection table is. Near-full tables lead to dropped connections.
kube-proxy restarts and CPU
Frequent restarts or sustained high CPU suggest misconfiguration, missing kernel features or oversized rule sets.
A troubleshooting path for an unreachable Service
Confirm the Service has ready endpoints
If the EndpointSlices are empty, the problem is label selectors or readiness checks, not kube-proxy.
Reach a Pod directly by its address
If direct connections fail, the issue lies in Pod networking or network policy, not in Service translation.
Reach the Service by its virtual address, not its name
If the address works but the name does not, DNS is the culprit. If neither works, translation on the node is suspect.
Check kube-proxy on the affected node
Look at its logs and health. Find out whether it is running, whether the mode loaded correctly and whether syncs succeed.
Inspect the kernel’s view
Look at the node’s rules, virtual server table or eBPF maps to see whether the Service and its Pods actually appear there, using the tool that fits your engine.
Check conntrack and node-level limits
A full table or restrictive firewall rules outside kube-proxy’s control can mimic Service failures.
Typical symptoms and causes
| Symptom | Likely cause | Where to look |
|---|---|---|
| Service works from some nodes but not others | kube-proxy stale or failing on those nodes | Last sync time and logs on the affected nodes |
| Intermittent DNS timeouts | Conntrack pressure or UDP stale entries | Conntrack usage and drops, cleanup behaviour |
| New Pods slow to receive traffic | Slow rule programming | Sync duration and programming latency |
| After switching to IPVS, nothing works | Missing kernel modules or leftover rules | Module list, kube-proxy startup logs |
| Backend sees node address instead of client | Masquerading under the Cluster policy | Traffic policy and plugin settings |
| Long idle connections drop in IPVS | IPVS connection timeouts | Timeout settings and keepalive use |
Raise log verbosity only while investigating, because detailed logs are very large. Record mode changes and configuration edits with who made them and when, so you can connect a behaviour change to a cause.
18Design Patterns and Best Practices
Habits that keep Service networking boring, which is exactly what you want.
Patterns that work
Start simple, measure, then change
Run the default mode, publish dashboards for sync time and programming latency, and only consider another engine when those numbers cross a threshold you have agreed in advance.
Use readiness checks that mean something
Because readiness decides when a Pod enters the rules, make sure it reflects true ability to serve. A weak check sends traffic to Pods that cannot handle it, and a failing check can cause flapping and rule churn.
Add a pre-stop pause
Let Pods continue serving briefly after being told to stop, so that rule updates have time to reach all nodes before the Pod disappears.
Spread Pods across nodes and zones
Even spread makes the Local traffic policy and zone-aware routing work well and reduces the blast radius of a node failure.
Standardise the node image
Keep kernel version and required modules consistent across nodes, so that switching or tuning modes behaves predictably everywhere.
Version-control the proxy configuration
Treat kube-proxy settings as reviewed, versioned configuration and roll changes out gradually, never by editing a live node.
Do
- Monitor sync time, programming latency and conntrack usage
- Test any mode change on a canary node pool first
- Size the connection tracking table for your peak traffic
- Keep kernel, modules and kube-proxy versions aligned
- Use topology-aware options to keep traffic local when possible
- Restrict NodePort and external address exposure
Do not
- Switch engines because of a benchmark you did not reproduce
- Assume kube-proxy enforces network policy
- Run kube-proxy and an eBPF replacement together on the same node
- Ignore UDP and DNS when planning conntrack capacity
- Leave Services with huge endpoint counts untuned
- Expose NodePorts broadly without a firewall in front
19Common Mistakes and Anti-Patterns
Failures seen again and again in real clusters.
Problem
The team switches every node to IPVS at once because an article said it is faster, without checking kernel modules.
Why It’s Harmful
Nodes missing the needed modules fail to program Services correctly, and traffic between applications breaks cluster-wide within minutes.
Correct Approach
Verify prerequisites on every node image, rehearse in a test cluster, and roll out in stages from a small canary pool with a rollback ready.
Problem
Conntrack table size is left at defaults on nodes that handle heavy DNS and short-lived connections.
Why It’s Harmful
When the table fills, new connections are dropped. Symptoms look like random application errors and DNS timeouts, and no single log line explains them.
Correct Approach
Size the table for peak load, watch its usage as a standard node metric, and alert well before it fills.
Problem
Applications cache the resolved address of a Service forever or store individual Pod addresses.
Why It’s Harmful
Pods change constantly, so cached Pod addresses go stale. Even a cached Service address can misbehave if the Service is deleted and recreated with a new address.
Correct Approach
Connect by Service name, honour DNS time-to-live values, and reconnect gracefully after failures.
Problem
Local traffic policy is turned on for an externally exposed Service whose Pods sit unevenly across nodes.
Why It’s Harmful
Nodes with few Pods receive a similar share of traffic to nodes with many, so some Pods are overloaded while others idle, and nodes with no Pods may be sent traffic if health checks are misconfigured.
Correct Approach
Spread Pods evenly with topology rules, make sure load balancer health checks use the node health port, and watch per-Pod load.
Problem
Operators hand-edit firewall or packet rules on nodes to “fix” a Service problem.
Why It’s Harmful
kube-proxy owns its rules and will rewrite or conflict with manual changes, and other node software may also edit the same tables. The result is rules that work until the next sync and then break again.
Correct Approach
Fix the cause at its source, whether configuration, labels or policy, and let kube-proxy or the network plugin own the rules for Services.
Problem
Teams blame kube-proxy for blocked traffic that is really a network policy, a firewall or a security group.
Why It’s Harmful
Hours go into inspecting Service rules, while the actual block sits in a different layer and stays untouched.
Correct Approach
Follow the troubleshooting path from endpoints to direct Pod reachability to Service address to node rules, so each layer is ruled in or out in turn.
20Advantages, Disadvantages and Trade-offs
A final balance sheet for the whole kube-proxy approach and its alternatives.
Advantages of kernel-level Service handling
- No extra hop and no central bottleneck, since every node translates locally
- Works without changing applications, which just use a name
- Survives kube-proxy restarts because rules live in the kernel
- Choice of engines to fit cluster size and operating style
- Rich integration with traffic policies, topology hints and graceful termination
- eBPF options add observability and avoid per-packet rewriting for in-node clients
Disadvantages / Trade-offs
- Every node must hold the state for every Service, costing memory and sync work
- Connection-level balancing only, so requests on one long connection stay on one Pod
- Rule-based engines struggle at very large scale
- Debugging requires kernel-level skills that differ per engine
- Engine changes affect the whole cluster’s networking
- eBPF replacement adds plugin dependency and kernel requirements
Connection-level balancing is not request-level
A subtle limit worth repeating: kube-proxy engines balance new connections, not individual requests. If a client opens one long-lived connection, such as with some modern protocols that multiplex many requests, all those requests go to the same Pod. Teams that need request-level balancing, retries or fine-grained traffic shaping turn to ingress controllers, gateways or service meshes on top of Services, not instead of them.
Choosing between the options
| Situation | Best fit | Why |
|---|---|---|
| Small or medium cluster, no performance complaints | iptables | Simple, universal, proven |
| Large cluster on a kernel with IPVS support, needs balancing choices | IPVS, with awareness of its future | Constant-time lookups and algorithm choice |
| Large cluster on a recent Kubernetes version, wants to stay with kube-proxy | nftables | Scales well and is the modern in-kernel path |
| Wants visibility, identity-aware policy and the lowest overhead from one layer | eBPF kube-proxy replacement | Single data plane, connect-time translation |
| Managed cluster with limited control | Provider’s supported option | Supportability matters more than theoretical speed |
21Frequently Asked Questions
Questions that beginners and experienced engineers keep asking.
In the modes used today, no. kube-proxy only programs the kernel, and the kernel moves the packets. If kube-proxy stops, existing rules keep working with their last known state, though updates stop until it returns.
No. eBPF-based Service handling comes from a network plugin that replaces kube-proxy. You enable the plugin’s replacement feature and do not run kube-proxy on those nodes.
Because the address is virtual and the rules usually match only the specific protocol and port of the Service. A ping uses a different protocol, so nothing translates it. Test with a real connection to the Service port instead.
Generally yes, since each node translates traffic that leaves it, which is why gradual migration by node pool works. It does make troubleshooting harder, so keep mixed periods short and well labelled.
They can. Mappings live in kernel tables owned by the old engine, so switching may drop or orphan some connections. Roll nodes gradually, drain them when possible, and expect clients to reconnect.
Not always. In small clusters the difference is usually invisible. The advantage appears when the number of Services is large and rule programming time dominates. Benchmark your own workload.
Under the Cluster traffic policy, nodes mask the client address when forwarding to a Pod on another node. Use the Local policy or an eBPF data plane that avoids masquerading if you need real client addresses.
In rule-based modes, usually rule programming time and CPU use of kube-proxy, which shows up as slow reachability of new Pods. Packet speed degrades later. Connection tracking limits are another early casualty on busy nodes.
No. Headless Services have no virtual address, so DNS returns Pod addresses directly and clients connect to Pods without any Service translation.
No. It balances new connections. Many requests on one connection all go to the same Pod. For request-level balancing, use an ingress, gateway or service mesh layer.
Start from what your platform supports and what the current release notes recommend. Many teams begin with the default, then consider nftables or an eBPF plugin if they need more scale or visibility. Avoid building new plans around a mode that your version’s notes mark as deprecated.
Usually yes, because existing rules stay in the kernel while it restarts. The risk is the gap during which Service changes are not applied, and the full resync on startup, so restart nodes gradually rather than all at once.
22Summary and Key Takeaways
Pods are temporary, so Kubernetes gives groups of them a stable virtual address through Services, and keeps the list of ready Pods in EndpointSlices. kube-proxy watches those objects on every node and writes instructions into the kernel so that traffic to a Service address is redirected to a healthy Pod. In the rule-based modes the kernel does the rewriting, kube-proxy only programs it, and connection tracking remembers each choice. The iptables mode is simple and universal but slows as the number of Services grows. IPVS uses hash tables and offers several balancing algorithms but needs extra kernel pieces and has an uncertain future. nftables keeps the in-kernel model but uses maps for constant-time lookups and atomic updates. eBPF replacement, provided by network plugins, removes kube-proxy and translates Services at connect time or at the network device.
Come back to the helpline at the start. The number stays fixed, the agents come and go, and the switchboard keeps its list current. What differs between the engines is how that switchboard is built: a long checklist, an indexed machine, a phone book or a smart gadget at the door. The best choice is the simplest one that your measurements show you can live with, adopted gradually, watched carefully and understood well enough to debug at three in the morning.
Key Takeaways
- kube-proxy programs, the kernel forwards — it translates Services and EndpointSlices into kernel instructions and does not carry your packets in modern modes.
- iptables is the safe default — universal and well understood, but rule matching and rewriting grow with the number of Services, which hurts at large scale.
- IPVS and nftables fix the scaling curve — hash and map lookups with incremental or atomic updates, with IPVS offering more balancing algorithms and nftables avoiding extra kernel modules.
- eBPF replaces rather than tunes kube-proxy — a network plugin keeps its own maps, translates at connect time or at the network device, and brings observability along with plugin dependency.
- Separate data path from control path — decide whether packet handling or rule programming is your real bottleneck before choosing an engine.
- Mind the edges — traffic policy, source addresses, session affinity, conntrack size and graceful termination cause more real incidents than the engine itself.
- Migrate gradually and measure — canary a node pool, watch sync time, programming latency and conntrack, keep one owner of Service translation per node, and keep a rollback ready.