Kubernetes gives an internal source IP although externalTrafficPolicy is set to Local
Master System Design with Codemia
Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.
Introduction
externalTrafficPolicy: Local preserves the original client source IP only in a specific traffic path: the packet must arrive at a node that has a local endpoint for the Service, and the infrastructure in front of that node must not terminate and re-originate the connection. If you still see an internal IP, something else in the path is rewriting or proxying the request.
This is why the setting feels confusing: it affects kube-proxy behavior for a Service, but it does not magically disable every other hop in front of your pod.
What externalTrafficPolicy: Local Actually Does
For a NodePort or LoadBalancer Service, the default Cluster policy allows traffic to land on one node and then be forwarded to endpoints on another node. That forwarding can require source NAT, so the pod may see the node IP instead of the client IP.
With Local, kube-proxy only sends external traffic to endpoints on the same node. Kubernetes documents this as the way to preserve the original source IP for Service traffic.
A minimal Service looks like this:
That is necessary, but not always sufficient.
Why You May Still See an Internal IP
There are several common reasons.
The Request Is Going Through Another Proxy
If traffic first passes through an ingress controller, CDN, reverse proxy, WAF, or L7 load balancer, your pod may see the proxy's address rather than the original client address. In that case externalTrafficPolicy is not broken; you are simply no longer looking at raw client-to-Service traffic.
When a proxy terminates HTTP and opens a new connection to the backend, the original client IP often moves into a forwarded header such as X-Forwarded-For. Your application then has to trust and parse that header correctly.
The Node Receiving Traffic Has No Local Endpoint
With Local, a node without a local backend should not forward traffic to another node. Kubernetes docs show that such traffic is dropped for NodePort, precisely to preserve client IP instead of proxying across nodes.
That means you should verify endpoint placement:
If the external load balancer still sends traffic to nodes without endpoints, the platform health-check integration may be misconfigured or unsupported.
Your Cloud Provider Does Not Preserve Source IP in That Setup
Kubernetes supports the field, but cloud-provider implementations differ. The official source-IP tutorial explicitly notes that only some providers preserve source IP for LoadBalancer Services. On platforms such as GKE, switching to Local causes nodes without endpoints to fail a health check so they are removed from the load balancer backend set.
You can inspect that behavior here:
If your provider or load balancer integration does not honor this model, the packet may still be proxied or NATed before it reaches the pod.
You Are Looking at the Wrong Address in the Application
Some applications log the peer address from a sidecar proxy, not from the original request. Others run behind service meshes or HTTP proxies that normalize headers. In those cases the "source IP" in your logs may be the sidecar, the node, or the internal load balancer hop.
So separate these questions:
- what address does the TCP connection peer show
- what address does the forwarded header show
- which one does the application actually trust
Without that distinction, debugging becomes guesswork.
A Practical Debugging Workflow
Use a simple echo service first, before debugging your real app:
Then verify:
If the echo service shows the client IP correctly but your real app does not, the issue is in your ingress, sidecar, or application layer. If the echo service still shows an internal address, focus on the Service path and provider integration.
What Local Does Not Guarantee
It does not guarantee even traffic distribution. In fact, it can reduce distribution because only nodes with local endpoints should receive traffic.
It also does not preserve source IP through arbitrary reverse proxies, nor does it apply to traffic that never reaches the Service directly.
Finally, it does not fix application code that ignores forwarded headers when a proxy is intentionally in front of the pod.
Common Pitfalls
The biggest pitfall is assuming externalTrafficPolicy: Local affects ingress controllers and HTTP proxies. It only changes Service routing behavior.
Another is forgetting that nodes without local endpoints should be excluded. If traffic still reaches them, investigate health checks and provider support.
Teams also debug using a full application stack too early. Start with an echo server so you can isolate network behavior from application behavior.
Finally, do not assume the logged address is the true client address unless you know whether a proxy or service mesh sits in front of the app.
Summary
- '
externalTrafficPolicy: Localpreserves client IP only for supported Service traffic paths.' - If you still see an internal IP, a proxy, load balancer, or unsupported provider path is usually involved.
- Verify local endpoints, load balancer health checks, and provider support first.
- Use an echo service to separate Kubernetes networking from application-layer logging.
- When an L7 proxy is in the path, the real client address may live in forwarded headers rather than the socket peer.

