**Hairpinning** (also known as NAT loopback) occurs when an in-cluster pod connects to a Kubernetes Service using its public Load Balancer IP address or domain name rather than its internal `ClusterIP`. Common examples include an ingress controller health-checking itself through its public domain, or microservices calling shared endpoints via external URLs. On many Kubernetes platforms, hairpin connections fail or time out because internal packet routing conflicts with external proxy configurations. On Syself Autopilot, hairpinning works out of the box because the Hetzner Cloud Controller Manager (CCM) automatically sets the Service's `ipMode` based on your configuration. ```mermaid flowchart LR P["Client Pod"]:::app -->|"1. Connects to external LB IP"| LB["Hetzner Load Balancer"]:::external LB -->|"2. Routes connection back to cluster"| B["Target Pod"]:::app ``` ## How `ipMode` determines the traffic path The CCM writes `status.loadBalancer.ingress[*].ipMode` on the Service, and its value determines how hairpin traffic is routed. - **Standard mode (`ipMode: VIP`):** When [PROXY protocol](/docs/hetzner/apalla/network/load-balancing/proxy-protocol) is not enabled, the CCM assigns `ipMode: VIP`. Cilium eBPF intercepts traffic to the load balancer IP directly in the Linux kernel on the node and routes it straight to a backend pod. The external Hetzner Load Balancer is bypassed completely, minimizing latency. - **Proxy mode (`ipMode: Proxy`):** When PROXY protocol is enabled (`uses-proxyprotocol: "true"`), the CCM automatically configures `ipMode: Proxy`. Cilium does not short-circuit the connection locally; instead, traffic leaves the node and reaches the external Hetzner Load Balancer, which injects the required PROXY protocol header before routing the request back into the cluster. The CCM manages `ipMode` automatically based on the `uses-proxyprotocol` service annotation . ## When hairpinning fails Hairpin failures typically stem from configuration mismatch where your backend application (such as an ingress controller) expects PROXY protocol headers, but the Service's `ipMode` is set to `VIP` instead of `Proxy`. In this scenario, Cilium intercepts the in-cluster traffic locally and routes it straight to the backend pod without passing through the Hetzner Load Balancer. Because the connection never touches the load balancer, no PROXY header is attached. The backend pod receives raw request bytes where it expected a PROXY header, treats the input as invalid protocol data, and immediately drops or rejects the connection (often causing timeouts or `400 Bad Request` errors). > [!NOTE] > On standard Syself Autopilot clusters, the CCM automatically sets `ipMode: Proxy` whenever `uses-proxyprotocol: "true"` is set. This failure only occurs if `ipMode` is overridden or managed by an outdated controller. ## Verify it Once the CCM has processed the Service, read the field: ```console $ kubectl get svc -o jsonpath='{.status.loadBalancer.ingress[0].ipMode}' Proxy ``` With PROXY protocol enabled, expect `Proxy`. Without it, expect `VIP`. If you set `load-balancer.hetzner.cloud/hostname` on the Service, the ingress entry carries only the hostname and this command returns nothing. If a hairpin call still times out, [debug the node's network path](/docs/hetzner/apalla/network/debug/debug-node-networking).