Enable PROXY protocol
PROXY protocol makes the Hetzner load balancer pass the real client IP to your pods. Without it, every request appears to originate from the load balancer, which breaks rate limiting, WAF rules, audit logs, and any NetworkPolicy that matches on source IP.
Set it on both sides: the load balancer adds the header, and the backend reads it. If only one side is enabled, every connection breaks.
Turn it on#
Enable it at the load balancer #
Add the annotation to the type: LoadBalancer Service:
metadata:
annotations:
load-balancer.hetzner.cloud/uses-proxyprotocol: "true"
Tell the backend to read it #
The backend side depends on what receives the connection.
Set use-proxy-protocol in the controller ConfigMap:
controller:
config:
use-proxy-protocol: "true"
Enable proxyProtocol on the entryPoint the load balancer targets (web or websecure). Traefik only trusts PROXY headers from IPs you list, so set trustedIPs to cover the sources that reach the entryPoint.
An application that reads the connection directly must parse the PROXY protocol header itself, before it reads the request bytes. Use a PROXY protocol library for your language rather than parsing the header manually.
controller:
replicaCount: 2
config:
use-proxy-protocol: "true"
service:
annotations:
load-balancer.hetzner.cloud/location: fsn1
load-balancer.hetzner.cloud/uses-proxyprotocol: "true"
Both the Service annotation and the ConfigMap value are present, so the two sides agree.
What else it turns on#
Enabling PROXY protocol also causes the CCM to set the Service's ipMode to Proxy, which keeps a pod calling its own load balancer IP working. You do not set that yourself. See Handle hairpinning for why it matters.
By default the load balancer hides the caller. When a client connects, the load balancer opens a fresh connection to your node and puts its own address on it (SNAT, source network address translation). The application at the other end sees the load balancer as the source and cannot determine who actually called.
PROXY protocol addresses this. As the load balancer forwards the connection, it prepends a short header carrying the client's real IP and port. The backend reads that header first, then treats the connection as if it came directly from the client.
Kubernetes can lose the address a second time, inside the cluster. It depends on the Service's externalTrafficPolicy:
Cluster(the default): the node that receives the packet may forward it to a pod on another node, and it source-NATs the packet on the way, so the pod sees the node's address, not the client's.Local: the node does not source-NAT and does not forward across nodes. Only nodes that run a ready pod for the Service receive traffic, and the pod keeps the source it was handed.
On a Hetzner load balancer the outer address is already replaced before the packet reaches any node, so PROXY protocol is what recovers the real client IP. externalTrafficPolicy decides what happens after that, inside the cluster. For a deeper walk through both hops, see the Kubernetes Using Source IP tutorial and Preserve the client source IP .
Important
Enable PROXY protocol before you rely on anything that reads the client address: rate limits, a WAF, or source-IP network policies. Until both sides are set, those controls see the load balancer's address and behave as if every request came from one client.
For the wider picture, including the externalTrafficPolicy: Local alternative and how to verify what the pod sees, read Preserve the client source IP .
Tune health checks for node replacement
Set load balancer health checks so a node being drained and replaced leaves the target pool before it drops traffic.
Preserve the client source IP
Stop your pods from seeing the load balancer address instead of the real client, using PROXY protocol or externalTrafficPolicy Local.