Requirements
The server, operating system, memory, disk, network and DNS Kwerft needs, and what the installer changes on the host.
Kwerft installs onto one fresh Ubuntu server and grows from there. This page lists what that server needs before you run the installer, and what the installer changes on it. For the install itself, see Getting started.
The server
| Requirement | |
|---|---|
| Provider | A Hetzner Cloud server or a Hetzner dedicated (Robot) server |
| Operating system | Ubuntu 22.04, 24.04 or 26.04 LTS |
| CPU architecture | amd64 (x86_64) or arm64 (aarch64) |
| Memory | 8 GB recommended; 4 GB works with --lite |
| Disk | At least 30 GB free on / |
| Access | Root, or a user who can run sudo |
The installer tells Hetzner Cloud from dedicated servers by asking the Cloud metadata service. If that guess is wrong, pass --platform cloud or --platform dedicated.
Use a fresh server. The installer stops if something already listens on port 80 or 443 (for example nginx or Apache), and it expects to own Kubernetes on that machine.
Memory
The platform itself (k3s, Cilium, Traefik, cert-manager, VictoriaMetrics, VictoriaLogs and Kwerft) uses about 2.5 GB of RAM on an idle server. Each running Git build needs another 1–2 GB. That is why 8 GB is the recommended size.
The installer refuses servers with less than 4 GB and warns below 8 GB. On a 4 GB server, install with --lite:
- Hubble is off, so traffic rules show no allowed and dropped counts.
- Metrics are kept 7 days instead of 30, logs 3 days instead of 14.
That leaves room for a few small apps, not for much else.
Other checks
Before it changes anything, the installer’s preflight stage also checks that:
- the kernel runs cgroup v2 (the Ubuntu default),
- the server has a public IPv4 address,
- outbound HTTPS works (it downloads k3s, Helm charts and container images),
- the Kwerft release you asked for is published.
A failed check ends the run with exit code 10 (preflight) or 20 (network). See exit codes.
Network and ports
After the install, the host firewall lets these in from anywhere:
| Port | What for |
|---|---|
| TCP 22 | SSH |
| TCP 80 | HTTP: Let’s Encrypt HTTP-01 challenges and the redirect to HTTPS |
| TCP 443 | HTTPS: the console and your apps |
| UDP 51871 | WireGuard between nodes (Cilium) |
Everything else, including the Kubernetes API on 6443, is only reachable from the server’s private network and from pods. You can narrow SSH and open more ports later under Network › Server firewall.
If you use a Hetzner Cloud Firewall of your own in front of the server, it must let TCP 80 and 443 in, or certificates cannot be issued and nobody reaches the console.
Private network
A single server needs no private network. To add more servers to the same cluster later, they need one they share:
- Hetzner Cloud: attach the servers to the same Cloud Network.
- Dedicated servers: a vSwitch coupled to the Cloud Network.
The installer picks the first private (RFC 1918) address that is not on the default route as the node address. Pass --private-iface to choose the interface yourself. See Clusters & nodes.
DNS
DNS is optional for a first try. Without --domain, the console gets a temporary hostname <public-ip>.sslip.io, which the public sslip.io service resolves to your server. It works for trying Kwerft, not for production.
For your own hostname, create an A record pointing at the server’s public IPv4 address before or after the install, for example ops.example.com. Let’s Encrypt issues the console’s certificate once the record resolves to the server.
For apps, you have three options:
- A record per app hostname, pointing at the server.
- One wildcard record, such as
*.apps.example.com, that covers every app under an apps domain. - Let Kwerft keep the records in Hetzner DNS for you (zones in the Hetzner Console, with an API token).
Domains & TLS explains the apps domain, wildcard certificates and managed records.
What the installer changes on the host
The installer keeps its changes in files it owns, so you can see exactly what it did:
| Change | Where |
|---|---|
| Packages: curl, ca-certificates, jq, nftables, chrony, unattended-upgrades, open-iscsi | apt |
Swap turned off; swap lines commented out (# disabled by kwerft) |
/etc/fstab |
| Kernel modules overlay, br_netfilter, wireguard | /etc/modules-load.d/kwerft.conf |
IP forwarding, inotify and vm.max_map_count sysctls |
/etc/sysctl.d/90-kwerft.conf |
| chrony and unattended-upgrades enabled | systemd |
Host firewall: its own nftables table inet kwerft |
/etc/nftables.d/kwerft.nft, plus an include line in /etc/nftables.conf |
| k3s (embedded etcd, secrets encryption, no flannel, no kube-proxy) | /etc/rancher/k3s/config.yaml |
| Registry mirror for images built from Git | /etc/rancher/k3s/registries.yaml |
| AppArmor profile for rootless builds | /etc/apparmor.d/kwerft-buildkit |
| Helm | /usr/local/bin/helm |
| State, setup token, install log | /var/lib/kwerft, /etc/kwerft, /var/log/kwerft |
With --harden-ssh it also writes /etc/ssh/sshd_config.d/90-kwerft.conf, which turns off password logins and root password logins. Make sure your SSH key works before you use it.
The installer never edits the nftables rules or registries.yaml entries you wrote yourself. If registries.yaml exists and was not written by Kwerft, it leaves the file alone and prints the lines to add.
install.sh --uninstall removes k3s, the firewall table and these files again, keeping the logs. See the installer reference.