Workload · Running your own k3s cluster

k3s on bare metal: your own small Kubernetes cluster, set up safely

You can run k3s on bare metal with nothing more than a few Linux servers and the official install script: one machine becomes the server that runs the cluster, the rest join as workers with a shared token. Per the k3s docs, a server node needs only two cores with 2 GB of memory, and each worker one core with 512 MB. What deserves your evening is the network: our servers talk to each other over public addresses, so you close the cluster's ports to everyone but your own machines before you install. If you would rather not look after the cluster's brain at all, our managed Kubernetes on bare metal runs it for you in New York.

The short version
  1. Start with one server and a few workers; grow to three servers later
  2. Close ports 6443, 10250 and the overlay to everyone but your nodes
  3. Pick the encrypted wireguard-native network, since there is no private VLAN
  4. Guard the join token like a master key and back it up
  5. Prefer not to run the control plane? Managed clusters in New York

What you take on when you run k3s yourself

k3s packs a whole Kubernetes distribution into one binary and one install script. That makes the first install quick. Everything after it is yours: the control plane, its datastore and backups, the firewall on each machine, the upgrades, and whatever breaks at night.

That trade is worth it when you want to learn Kubernetes properly, need a city other than New York, or want full say over every setting. It is less worth it when the cluster is just a means to run your apps.

Will small servers be enough?

For k3s itself, yes. The requirements page lists these minimums, and says they cover k3s and its bundled parts only, not your apps:

  • Server node: two CPU cores, 2 GB memory.
  • Worker (agent) node: one core, 512 MB memory.
  • Disk: an SSD where possible, because k3s performance depends on its database.
  • Processor families: x86_64, armhf and arm64.
  • Names: every machine needs its own hostname; two nodes with the same one will not both join.

So size each worker for what you plan to run on it, and give the server a fast disk. Our dedicated servers are whole machines, so nothing else competes for those cores or that disk.

One server or three?

Start with one if you are learning or the apps can tolerate a pause. A single k3s server is already a complete cluster, keeping its state in an embedded SQLite database. If that machine goes down, though, the cluster's API goes with it until the server is back.

Go to three when the cluster has to keep answering through a failure. High availability in k3s uses embedded etcd, which keeps quorum only with an odd count of servers. The docs also describe the path between the two: restart an existing single server with --cluster-init and it converts to etcd, ready for two more servers to join with --server. The two new servers join with the same token file and the same --flannel-backend=wireguard-native flag as the first, since flannel settings must match on every server. Before they join, open 2379-2380/tcp between the three servers only, for example: for ip in 203.0.113.21 203.0.113.24 203.0.113.25; do ufw allow from "$ip" to any port 2379:2380 proto tcp; done.

Close the doors before you open the cluster

Each of our servers has one network port with a public address, and there is no private network between servers on sale. Your nodes will talk to each other across the public internet, which changes how you set them up:

  • 6443/tcp, the Kubernetes API: open only to your nodes and to the computer you run kubectl from.
  • 10250/tcp, the kubelet: open only to your nodes.
  • 8472/udp, the default VXLAN overlay: the k3s docs warn it must never face the world, since anyone who reaches it can get onto your cluster network.
  • 51820/udp, the WireGuard overlay: only between nodes (plus 51821 if you run IPv6).
  • 2379-2380/tcp, etcd: between servers only, once you have three.

Choose --flannel-backend=wireguard-native when you install. The docs describe it as encapsulating and encrypting the network traffic between nodes, which the default VXLAN does not. Make sure the WireGuard kernel module is available on every node before you switch it on. The k3s page suggests turning ufw off; on a server whose only address is public, keep it on and use the documented rules, limited to your own machines:

# on every node: your nodes' public IPs, then your own computer
for ip in 203.0.113.21 203.0.113.22 203.0.113.23; do
  for p in 6443/tcp 10250/tcp 51820/udp; do
    ufw allow from "$ip" to any port "${p%/*}" proto "${p#*/}"
  done
done
ufw allow from 198.51.100.7 to any port 6443 proto tcp
ufw allow from 10.42.0.0/16 to any   # pod network
ufw allow from 10.43.0.0/16 to any   # service network
ufw allow OpenSSH
ufw default deny incoming
ufw enable

Up and running in an evening

  1. Order the machines with a current Linux release from the Linux servers page and give each one a unique hostname. Racked machines take about 30 minutes from approval to your first login.
  2. Apply the firewall above on every machine.
  3. On the server, create a random token in a file only root can read, then install k3s with the WireGuard network.
  4. Copy the server's own token file to each worker and lock it to mode 600. That copy is a secure-format token, so a worker can confirm it is talking to your cluster before handing over credentials.
  5. Install each worker with K3S_URL pointing at the server. Network flags live on the server only, so workers need none.
  6. On the server, kubectl get nodes should list every machine as Ready.
# on the server
(umask 077; openssl rand -hex 32 > /root/k3s-token)
curl -sfL https://get.k3s.io | K3S_TOKEN_FILE=/root/k3s-token sh -s - server \
  --flannel-backend=wireguard-native
scp /var/lib/rancher/k3s/server/token root@203.0.113.22:/root/k3s-token

# on each worker, after: chmod 600 /root/k3s-token
curl -sfL https://get.k3s.io | K3S_URL=https://203.0.113.21:6443 \
  K3S_TOKEN_FILE=/root/k3s-token sh -

Keeping the token in a file, rather than typing it after K3S_TOKEN=, keeps it out of your shell history. To work from your laptop, copy /etc/rancher/k3s/k3s.yaml there and change its server address to the server's public IP.

Look after the keys and the backups

The k3s docs are plain about the server token: whoever holds it has, in effect, full admin rights over your cluster. It also unlocks the encrypted data inside the cluster's database, so a backup restored without the matching token is unusable. Keep a copy of the token with every backup.

The kubeconfig file is just as powerful, since it signs you in as the cluster's top administrator. On a single server, back up /var/lib/rancher/k3s/server/db with the token. With three servers, k3s takes etcd snapshots on its own every 12 hours by default and keeps five, on the server's own disk; copy them somewhere else, because a snapshot on the machine that died will not help you.

When you upgrade, re-run the install script with the same settings: servers first, one at a time, then workers.

When letting us run the brain makes more sense

If what you want is a place for your containers rather than a cluster to maintain, Kubernetes on bare metal, managed for you keeps the parts that matter to you and takes the rest:

  • We run the control plane, upstream Kubernetes built with RKE2, on one node for testing or three for high availability, with an etcd snapshot every six hours.
  • Your nodes are 1 to 10 of our New York servers that are ready now, already on Ubuntu 24.04 or 26.04, each billed as the server it is, with no setup fee.
  • You keep root SSH, the console and rescue mode on every node, and you start upgrades when it suits you.
  • A firewall on each node lets in only your cluster's machines. Traffic between nodes is not encrypted, so services that must talk privately use TLS or a service mesh.
  • Load balancers take the extra IPv4 addresses on your own nodes through MetalLB.

Building it yourself stays the right call if you need encrypted node-to-node traffic, a different city, or k3s specifically. For that, our bare metal for containers is the place to start.

Questions people ask

How many machines do I need for a small k3s cluster?
One is enough to start, because a single k3s server already runs everything a cluster needs. Add workers as your apps need room beyond that one machine. If the cluster must ride out a dead server, plan on three of them.
Is the traffic between my servers encrypted?
Not with the default VXLAN overlay. Install with the wireguard-native backend and k3s encrypts pod traffic between nodes. Either way, keep the cluster ports closed to everyone but your own machines.
How will visitors reach my apps without a cloud load balancer?
k3s ships its own: ServiceLB answers LoadBalancer services on the nodes' own ports, and by default the bundled Traefik listens on 80 and 443 across all nodes. Point your domain at a node's public address. Note that ufw has no say over those ports, because iptables rules that k3s writes itself redirect the traffic first. To keep some nodes off the web, put the svccontroller.k3s.cattle.io/enablelb=true label on just the nodes meant to host it, or install with --disable=traefik if you want no public web entry at all.
Why does the join token matter so much?
Anyone holding it can act as a cluster admin, and a backup of the cluster's database cannot be restored without it, because the token encrypts part of that data. That is why it belongs with your backups, and nowhere public. If it leaks instead, k3s token rotate issues a new one, and every node then has to be restarted with it.
Can I add more servers to a managed cluster later?
Yes. An order takes one to ten New York servers as nodes, and you can add more later, up to ten in total.

Get a Kubernetes cluster without running its brain

Pick the control plane, tick the New York servers that become your nodes, and download the kubeconfig when the cluster is ready.

Start your cluster

Read next