Kubernetes on a Hetzner Private Network
Secure Kubernetes on Hetzner Cloud
Running Kubernetes in the cloud is powerful — but exposing control-plane and worker nodes directly to the internet is risky. This full IaC (Infrastructure as Code) shows a clean, maintainable way to deploy a Kubernetes cluster on Hetzner Cloud where all nodes live exclusively on a private network. sEvery infrastructure element is defined as code using Terraform (OpenTofu) and Kubespray config.
Why This Setup Matters
- Security first: No public IPs on Kubernetes nodes → reduced attack surface
- Clean separation: administrator access via dedicated jumphost, outbound internet via gateway, inbound traffic via load balancer
- Fully automated: Infrastructure as Code + Configuration as Code
- Clean and Maintainable repository structure
- Cost-effective: Leverages Hetzner’s affordable cloud resources
- Repeatable: Clone, re-configure, deploy N+1
Architecture Overview
The setup consists of three layers:
Infrastructure (1-infra)
- Private Hetzner Cloud network
- Jumphost (public IP + SSH access)
- NAT Network gateway (public IP for NAT/outbound traffic)
- Hetzner Load Balancer (public IP, DNS, TLS termination)
Platform (2-platform)
- 3-node Kubernetes cluster (all nodes on private IPs only)
- Deployed automatically with Kubespray
- Cilium as CNI
Applications (3-apps)
- HAProxy ingress controller
- Example deployment (e.g., a simple example.com app) routed through the load balancer
Prerequisites
- A Hetzner Cloud account and project
- Terraform / OpenTofu installed
- Hetzner API token with read & write permissions
- Basic familiarity with SSH, Kubernetes, ansible, Kubespray
Setup Overview
Clone the repo: https://codeberg.org/tessellative/k8s-hetzner-private-net.git
Follow the setup commands in staging order in the README.md files, starting at the root level.
Setup Order
The repo is nicely split:
- ./1-infra/terraform/ → Run Terraform to create the network, jumphost, gateway, load balancer and the k8s node VMs.
- ./2-platform/ → Deploy the 3-node Kubernetes cluster using Kubespray. Access the cluster via sshuttle over the jumphost.
- ./3-apps/ → Deploy the ingress controller and the sample application
- Update your /etc/hosts file with the loadbalancer public IP and the DNS: test.example.com
- Open the http://test.example.com in the browser (This)
Custom DNS with TLS Certificate
This demo can be run behind a proper non-reserved domain with TLS certificates set up! Change and configure the following with your domain
- ./1-infra/terraform/5-load-balancer.tf
- Change the LB to serve TLS on port 443 -> resource "hcloud_load_balancer_service" "example-k8s01-lb-service"
- Configure the http.certificates section -> resource "hcloud_load_balancer_service" "example-k8s01-lb-service"
- Set the proper DNS name at -> resource "hcloud_managed_certificate" "custom-domain-com"
- 1-infra/terraform/6-dns-example.com.tf
- Add the new DNS Zone -> resource "hcloud_zone" "custom-domain-com"
- Add your DNS record to the zone -> resource "hcloud_zone_rrset" "A-custom-domain-com"
Final Thoughts
This project is a great starting template for anyone who wants to run Kubernetes on Hetzner without exposing nodes publicly. It demonstrates good practices: separation of concerns, private networking and full automation.
Happy deploying! 🚀