Hetzner load balancers in Syself Autopilot perform distinct roles depending on whether they serve platform infrastructure or application traffic. The control-plane load balancer sits in front of the Kubernetes API server and is managed through `Cluster` topology variables. Workload load balancers route public traffic directly to application pods and are configured through annotations on `type: LoadBalancer` Services. If you are deciding how to expose a specific workload, start with [Choose how to expose an application](/docs/hetzner/apalla/network/expose/choose-how-to-expose). | Kind | Who creates it | How you configure it | | --------------------------- | --------------------------------------------------------- | ------------------------------------------ | | Control-plane load balancer | Syself Autopilot, at cluster creation | Topology variables on the `Cluster` object | | Application load balancer | The Hetzner CCM, when you create a `LoadBalancer` Service | Annotations on the Service | ```mermaid flowchart LR A(["kubectl and API clients"]):::you --> CPLB["Control-plane LB :443"]:::external --> API["Control-plane nodes :6443"]:::platform U(["Internet clients"]):::external --> APPLB["Per-Service LB"]:::external --> NP["Node NodePort"]:::platform --> POD["Pods"]:::app ``` > [!NOTE] > Hetzner Load Balancers support TCP-based protocols only (including HTTP and HTTPS, which run on TCP at Layer 4). UDP is not supported. To expose a UDP service, route traffic directly to the nodes with a `NodePort` instead; see [UDP is not supported](/docs/hetzner/apalla/network/expose/choose-how-to-expose#udp-is-not-supported). ## The control-plane load balancer Syself Autopilot creates one Hetzner load balancer for the Kubernetes API server when it builds the cluster. It listens on port `443`, forwards to port `6443` on the control-plane nodes, and carries only API traffic. The Hetzner CCM does not manage this one. The platform creates and reconciles it from your topology variables, so you configure it on the `Cluster` object, not with Service annotations. ```yaml title="cluster.yaml" spec: topology: variables: - name: clusterLoadBalancerType value: lb21 # lb11 (default) | lb21 | lb31 - name: clusterLoadBalancerName value: my-apiserver-lb # optional; Syself names it if you omit this - name: region value: nbg1 # fsn1 (default) | nbg1 | hel1 | ash | hil ``` `clusterLoadBalancerType` sets the Hetzner product. A load balancer type is defined by two limits: how many **targets** (the servers behind it) it can hold, and how many simultaneous **connections** it can carry. | Type | Max targets | Max connections | | ------ | ----------- | --------------- | | `lb11` | 25 | 10,000 | | `lb21` | 75 | 20,000 | | `lb31` | 150 | 40,000 | For the control plane, `lb11` is sufficient for almost all deployments because standard control planes only maintain 3 to 5 nodes and rarely exceed 10,000 concurrent API connections. The same load balancer types and capacity thresholds apply to workload Services via the `load-balancer.hetzner.cloud/type` annotation. For up-to-date specifications and pricing, see [Hetzner Load Balancers](https://www.hetzner.com/cloud/load-balancer/). Changing the type on a running cluster resizes the load balancer in place, so it keeps its IP addresses. The `region` variable sets both the control plane's placement and the load balancer's region at once; the load balancer cannot live in a different region from the cluster. Read the current settings from the `HetznerCluster` object: ```console $ kubectl get hetznercluster -o jsonpath='{.spec.controlPlaneLoadBalancer}' ``` The output shows `enabled`, `algorithm`, `type`, `port`, `region`, `targetAddressFamily`, `enableProxyProtocol`, and the `healthCheck` settings. ## Application load balancers The Hetzner Cloud Controller Manager (CCM) automatically provisions a dedicated Hetzner Load Balancer whenever you create a Service with `type: LoadBalancer`. It registers cluster nodes as backend targets through an allocated `NodePort` and writes the assigned public IP addresses to `status.loadBalancer.ingress`. > [!NOTE] > Hetzner Cloud Load Balancers are the standard, managed way to expose `type: LoadBalancer` Services on the platform. However, you can also self-host an in-cluster load balancing controller like [MetalLB](https://metallb.io/) to announce Service IPs directly from your nodes. The remainder of this page covers tuning Hetzner's managed load balancers. ```yaml title="service.yaml" apiVersion: v1 kind: Service metadata: name: my-app annotations: load-balancer.hetzner.cloud/location: fsn1 load-balancer.hetzner.cloud/name: my-app-lb spec: type: LoadBalancer selector: app: my-app ports: - port: 80 targetPort: 8080 ``` The CCM reconciles a Service whenever it changes, so most annotation edits take effect within seconds. ### Defaults you get with no annotations The platform configures the CCM so a bare `LoadBalancer` Service already works: - **Dual-stack address.** Every load balancer receives a public IPv4 and a public IPv6 address, and bridges IPv6 clients to the IPv4-only nodes. Disable IPv6 per Service with `load-balancer.hetzner.cloud/ipv6-disabled: "true"`. See [Serve IPv6 clients](/docs/hetzner/apalla/network/load-balancing/serve-ipv6). - **Public-IP targeting.** The load balancer reaches nodes over their public IP, because the platform runs no private network. You never set `use-private-ip`. - **Default region.** A Service with no `location` receives a load balancer in the cluster's region. Set `location` regardless, so the region is explicit and does not drift from where your workload runs. ### Annotation reference Every annotation goes on `metadata.annotations` of the Service. | Annotation (`load-balancer.hetzner.cloud/…`) | What it does | Notes | | -------------------------------------------- | ------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------- | | `location` | Region for the load balancer (`fsn1`, `nbg1`, `hel1`, `ash`, `hil`) | Defaults to the control-plane load balancer's region. Set it explicitly. | | `type` | Product: `lb11`, `lb21`, or `lb31` | `lb11` is the default. | | `name` | Name shown in the Hetzner console | Optional. | | `algorithm-type` | Target selection: `round_robin` or `least_connections` | `round_robin` is the default. | | `ipv6-disabled` | `"true"` removes the public IPv6 | IPv6 is on by default. | | `hostname` | Publishes a hostname in `status.loadBalancer.ingress` instead of the IPs | With this set, the IP and `ipMode` reads on the Service return nothing. | | `uses-proxyprotocol` | `"true"` enables PROXY protocol | The backend must also parse the header. See [Enable PROXY protocol](/docs/hetzner/apalla/network/load-balancing/proxy-protocol). | | `health-check-protocol` | `tcp`, `http`, or `https` | `tcp` is the default. | | `health-check-interval` | Interval between checks, a Go duration such as `"15s"` | A bare number is rejected and the Service fails to reconcile. | | `health-check-timeout` | Timeout of a single check, a Go duration such as `"10s"` | | | `health-check-retries` | Failed checks before a target is marked unhealthy | | | `health-check-http-path` | Path requested by `http` and `https` checks | | | `robot-target-address-family` | Address family for bare-metal (Robot) targets | Platform default `ipv4`. See [Load balance to bare-metal nodes](/docs/hetzner/apalla/network/load-balancing/bare-metal-lb-targets). | ## Where to go next - [Tune health checks for node replacement](/docs/hetzner/apalla/network/load-balancing/health-checks-and-node-replacement) - [Preserve the client source IP](/docs/hetzner/apalla/network/load-balancing/preserve-client-source-ip) - [Serve IPv6 clients](/docs/hetzner/apalla/network/load-balancing/serve-ipv6) - [Load balance to bare-metal nodes](/docs/hetzner/apalla/network/load-balancing/bare-metal-lb-targets) For where load balancers sit in the wider networking picture, see [Networking](/docs/hetzner/apalla/concepts/internals/networking).