This page shows how to run a reverse proxy such as Traefik or nginx directly on your nodes with no Hetzner load balancer in front. For the usual setup with a load balancer, see [Install Traefik](/docs/hetzner/apalla/network/ingress/install-traefik). By default, every Syself node runs a default-deny host firewall. How you deploy the proxy decides whether you have to open ports in it yourself. With `hostPort` you do not. With `hostNetwork` you do, and the policy has to be written correctly or part of your traffic is dropped without an error. ## What changes without a load balancer Without a load balancer you own the address, the health checking, and the failover. Clients reach the nodes directly, and every node listed in DNS has to serve the request on its own. - Nothing checks health for you, so a failed node keeps receiving traffic until you change DNS. Run the proxy on more than one node and give it a PodDisruptionBudget, as in [Survive node drains](/docs/hetzner/apalla/workloads/production/survive-node-drains). - A bare-metal node keeps its address when it is reprovisioned. A cloud node gets a new one, so update its `A` record. - The proxy does see the client's real address, because nothing sits in front to rewrite it, so there is no PROXY protocol to set up. Your backends read the client from `X-Forwarded-For` as they would behind any proxy. See [Preserve the client source IP](/docs/hetzner/apalla/network/load-balancing/preserve-client-source-ip). ## Run the proxy with hostPort Leave `hostNetwork` off, which is the default, and publish 80 and 443 with `hostPort`. The node answers on both ports, and Cilium hands each request to the proxy's pod before the [host firewall](/docs/hetzner/apalla/security/ports-and-listeners) checks it, so no policy is required. The same bypass means no host rule can narrow who reaches those ports. The proxy answers on the public IP of every node it runs on, from anywhere on the internet. Restrict access in the proxy itself or with a [network policy](/docs/hetzner/apalla/security/segment-with-network-policies) selecting its pod. ```yaml title="traefik-values.yaml" service: enabled: false ports: web: hostPort: 80 websecure: hostPort: 443 nodeSelector: node.example.com/ingress: "true" ``` Set that label on the worker pool rather than with `kubectl label`, so nodes still carry it after they are replaced. See [Label nodes and assign roles](/docs/hetzner/apalla/servers-and-nodes/labels/label-nodes-and-assign-roles). ## If you need hostNetwork instead Some proxies run with `hostNetwork: true` to reach a network only the node can see. The request then ends at the node, where the host firewall applies and denies every port it is not configured to allow. To allow the proxy's ports, 80 and 443, see [Open a port on the nodes](/docs/hetzner/apalla/network/expose/open-a-port-on-the-nodes). With a Service in front of the proxy, the rule also needs `remote-node`, or forwarded requests are dropped silently. ### Bind ports 80 and 443 with hostNetwork A `hostNetwork` pod binds its ports in the node's namespace, where anything below 1024 is privileged. Most proxy images run as non-root, so they cannot bind 80 or 443 there. A chart that drops all capabilities strips `NET_BIND_SERVICE` even from root, so grant it back. For the Traefik chart, override the ports and set both security contexts. `podSecurityContext` runs the pod as root, and `securityContext` adds the capability back to the container: ```yaml title="traefik-values.yaml" hostNetwork: true ports: web: port: 80 websecure: port: 443 podSecurityContext: runAsUser: 0 runAsGroup: 0 runAsNonRoot: false securityContext: capabilities: drop: [ALL] add: [NET_BIND_SERVICE] ``` Traefik's own defaults are 8000 and 8443, which are above 1024 and need none of these settings. Override them only when the node itself has to answer on 80 and 443. ## Verify it From outside the cluster, request each node IP you published: ```console $ curl -sS -o /dev/null -w '%{http_code}\n' http:/// 200 ``` Any answer from the proxy means the path works, including a `404` before you add a route. A timeout means the host firewall dropped the packet. A refused connection means nothing was listening on that port. [Troubleshoot load balancers and Services](/docs/hetzner/apalla/network/debug/troubleshoot-load-balancers-and-services) shows how to read the drops. ## Related - [Choose how to expose an application](/docs/hetzner/apalla/network/expose/choose-how-to-expose), the options with a load balancer - [Install Traefik](/docs/hetzner/apalla/network/ingress/install-traefik), the same proxy behind a load balancer - [Networking and Cilium](/docs/hetzner/apalla/concepts/internals/networking), the datapath and the two network layers