A Hetzner load balancer serves bare-metal (Robot) nodes exactly like Cloud nodes. The CCM handles both natively, so there is nothing extra to do for bare metal. Point a `type: LoadBalancer` Service at a pool that includes Robot servers and it works. The rest of this page explains how the CCM adds each kind and how the standard settings behave, so a mixed pool is easy to reason about. ## Server targets and IP targets The CCM adds the two machine kinds as different target types: - **Hetzner Cloud nodes join as server targets.** The CCM references the cloud server by its ID, and Hetzner tracks it. - **Bare-metal (Robot) nodes join as IP targets.** The CCM adds the Robot server by its public IPv4 address. Both back the same `type: LoadBalancer` Service, so a mixed pool of Cloud and Robot nodes works, and the load balancer spreads traffic across both. ## Bare-metal targets stay IPv4 By default the platform pins bare-metal targets to IPv4, with `HCLOUD_LOAD_BALANCERS_ROBOT_TARGET_ADDRESS_FAMILY=ipv4` on the CCM. This is the same reason the rest of the cluster runs IPv4: nodes have no usable IPv6, so an IPv6 target would never pass its health check and would remain permanently unhealthy. On an IPv4-only cluster the default is the correct setting, and you leave it unchanged. You can set the family per Service with the `robot-target-address-family` annotation: ```yaml metadata: annotations: load-balancer.hetzner.cloud/robot-target-address-family: ipv4 ``` ## externalTrafficPolicy on a bare-metal pool Prefer `externalTrafficPolicy: Local`. It removes a hop and keeps the load balancer's health check tied to a locally ready pod. It does not recover the client's source address: the Hetzner load balancer already replaced the source with its own IP before the packet reaches any node, so neither policy shows the pod the real client. Use PROXY protocol for that. - **`Local` (preferred):** the load balancer sends each request directly to a node that runs a ready pod, with no extra node-to-node hop and no in-cluster source-NAT. The load balancer health-checks each node and holds only the nodes that run a pod in the pool. For an ingress setup, those are the nodes running the reverse-proxy pod. - **`Cluster`:** any target node accepts the request and may forward it to a pod on another node, which adds a hop and source-NATs the packet inside the cluster. Either way the pod sees the load balancer's address, not the client's. `Local` means a targeted node must run a ready pod, or it drops out of the pool. On a fixed set of Robot servers, place the workload so that every server that should take traffic runs a pod. See [Preserve the client source IP](/docs/hetzner/apalla/network/load-balancing/preserve-client-source-ip) for how to recover the real client address, and [Tune health checks for node replacement](/docs/hetzner/apalla/network/load-balancing/health-checks-and-node-replacement) for the pool churn. For why you would run bare metal at all, and how to add it, see [Bare metal, cloud, and accelerators](/docs/hetzner/apalla/concepts/internals/bare-metal-and-cloud) and [Add bare-metal servers](/docs/hetzner/apalla/servers-and-nodes/provision/add-bare-metal-servers).