Expose a reverse proxy without a load balancer
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 .
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 .
- A bare-metal node keeps its address when it is reprovisioned. A cloud node gets a new one, so update its
Arecord. - 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-Foras they would behind any proxy. See Preserve the 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 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 selecting its pod.
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 .
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 . 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:
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:
$ curl -sS -o /dev/null -w '%{http_code}\n' http://<node-ip>/
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 shows how to read the drops.
Related
- Choose how to expose an application , the options with a load balancer
- Install Traefik , the same proxy behind a load balancer
- Networking and Cilium , the datapath and the two network layers
Expose an app with a LoadBalancer Service
Place a single application on the internet with a type LoadBalancer Service that the Hetzner cloud-controller-manager turns into a load balancer.
Open a port on the nodes
Allow a port in the default-deny host firewall with a CiliumClusterwideNetworkPolicy, so a hostNetwork workload is reachable from outside the cluster.