K3s Multi-Node: Adding a Second Machine to Your Cluster

From a single node to a real cluster in about ten minutes

A single-node K3s install is a wonderful thing. It’s Kubernetes that fits in a few hundred megabytes of RAM, it boots in seconds, and it lets you learn the whole ecosystem on one spare machine. But a cluster of one is a contradiction in terms. The day a sensible person finally gives in and orders a second mini PC — or rescues an old laptop from the cupboard — is the day K3s starts being genuinely useful. Pods can be rescheduled, you can drain a node to do maintenance, and a single dead disk stops being the end of the world.

Going from one node to two is delightfully undramatic. K3s was built for exactly this. Here’s how it actually works, and the handful of things that bite people.

Advertisement

K3s nodes come in two flavours. A server runs the control plane — the API server, scheduler, controller manager and the embedded datastore. An agent runs nothing but the kubelet and your workloads; it phones home to a server for instructions. Your existing single node is already a server. The simplest multi-node setup is to keep that server and bolt on an agent.

To join, an agent needs two things: the server’s URL and the cluster’s node token. The token lives on the server here:

sudo cat /var/lib/rancher/k3s/server/node-token

Copy that value. While you’re on the server, note its LAN IP — say 192.168.1.10. That’s the address the agent will connect to, so it must be a stable one. Static IP or a DHCP reservation; do not skip this.

On the new machine, the install is a single command with two environment variables:

curl -sfL https://get.k3s.io | \
  K3S_URL=https://192.168.1.10:6443 \
  K3S_TOKEN="<the-node-token-you-copied>" \
  sh -

Because K3S_URL is set, the installer knows this is an agent, not a new server, and configures it to register against your existing control plane. Give it thirty seconds, then check from the server:

$ kubectl get nodes -o wide
NAME      STATUS   ROLES                  AGE   VERSION
node01    Ready    control-plane,master   42d   v1.28.5+k3s1
node02    Ready    <none>                 38s   v1.28.5+k3s1

That <none> under ROLES is correct — it’s a worker. You now have a real cluster. The scheduler will start placing new pods across both nodes automatically.

This is where the ten-minute job becomes a forty-minute job if you’re unlucky. K3s’s default Flannel CNI uses VXLAN over UDP port 8472, the agent talks to the API on 6443, and the kubelet metrics flow back on 10250. If you run a firewall — and you should — open those between your nodes:

sudo ufw allow from 192.168.1.0/24 to any port 6443 proto tcp
sudo ufw allow from 192.168.1.0/24 to any port 10250 proto tcp
sudo ufw allow from 192.168.1.0/24 to any port 8472 proto udp

Symptoms of getting this wrong are maddeningly specific: the node shows Ready, but pods on different nodes can’t reach each other, or logs and kubectl exec hang. Nine times out of ten it’s the VXLAN port silently dropped. The second classic trap is mismatched clocks — if the two machines’ times drift, TLS handshakes fail with confusing certificate errors. Run an NTP client on both and forget about it.

The moment you have two nodes, the default local-path storage provisioner becomes a quiet liability. It writes volumes to whichever node the pod first landed on. Reschedule that pod to the other node and the data simply isn’t there. For stateless workloads this is fine. For anything with a database, you now need either node affinity to pin it down or proper distributed storage. That’s a bigger conversation — for now, just know that two nodes plus local-path plus a wandering database is a data-loss trap waiting to spring.

Adding an agent gives you workload resilience: lose the agent and pods reschedule onto the server. But the server is still a single point of failure — kill it and the whole control plane goes dark. True HA needs an odd number of servers (three is the magic number) with an embedded etcd quorum, started with the --cluster-init flag. That’s a worthwhile next step, but it’s a different job. For a homelab, one server and one or two agents is a perfectly honest setup.

Adding a second node to K3s is one of the highest reward-to-effort tasks in self-hosting. For the cost of a cheap mini PC and ten minutes, you go from a fragile single box to something that survives a node reboot without taking your services down. Just respect the firewall ports and the clock, keep stateful workloads on a leash until you’ve sorted real storage, and don’t mistake a worker node for high availability. Get those right and your little cluster will quietly carry on when one machine doesn’t — which is the entire point of running Kubernetes in the first place.

Advertisement

Related Content

Advertisement
Smarc
Written by Smarc

Founder and editor of vo.rs. A lifelong tinkerer who self-hosts far more than is sensible, hardens Linux boxes for fun, and prods the latest AI tools to see what they can really do. The how-to guides here are the notes Smarc wishes had existed the first time round.