Setting up a Debian VPS for k0s: sysctl, kernel modules, and the install
Published: 2026-04-05
Everything starts from a fresh Debian 12 Minimal image. The 01-prepare-vm.sh script turns it into a k0s-ready host in about 10 minutes. Here's what it actually does and why each step matters.
Swap
bashswapoff -a
sed -i '/\sswap\s/d' /etc/fstab
Kubernetes schedulers assume memory limits are hard. With swap enabled, a pod that OOMs may swap instead of getting killed, causing unpredictable latency spikes and making resource accounting useless. k0s warns on startup if swap is on.
sysctl tuning
Written to /etc/sysctl.d/99-k0s.conf:
# Required for Kubernetes networking
net.bridge.bridge-nf-call-iptables = 1
net.bridge.bridge-nf-call-ip6tables = 1
net.ipv4.ip_forward = 1
net.ipv6.conf.all.forwarding = 1
# For strongSwan IKEv2 VPN
net.ipv4.conf.all.accept_redirects = 0
net.ipv4.conf.all.send_redirects = 0
net.ipv4.ip_no_pmtu_disc = 1
# Performance tuning
net.core.somaxconn = 32768
fs.inotify.max_user_watches = 524288
fs.inotify.max_user_instances = 512
Without ip_forward, pods can't reach the internet. Without bridge-nf-call-iptables, kube-proxy's iptables rules won't fire on bridged (pod-to-pod) traffic.
The inotify limits matter for k0s: the controller watches a large number of files and ConfigMaps. The default Linux limit of 8192 causes silent failures on busy clusters.
Apply:
bashsysctl --system
Kernel modules
Loaded at boot via /etc/modules-load.d/k0s.conf:
br_netfilter
overlay
ip_tables
iptable_nat
iptable_filter
nf_conntrack
br_netfilter— enables iptables to see bridged trafficoverlay— required for containerd's overlayfs storage driverip_tables,iptable_nat,iptable_filter— kube-proxy depends on thesenf_conntrack— connection tracking, required for NAT
Without overlay, containerd falls back to a slower storage driver or fails entirely.
Load immediately:
bashmodprobe br_netfilter overlay ip_tables iptable_nat iptable_filter nf_conntrack
System packages
bashapt-get install -y \
curl wget gnupg2 ca-certificates \
iptables ipset \
nftables \
socat \
conntrack \
ipvsadm
ipvsadm for IPVS mode (if used). conntrack for the netfilter connection tracking tool.
k0s installation
bashcurl -sSLf https://get.k0s.sh | sh
k0s install controller --single
--single means this node runs both controller and worker. k0s installs itself as systemd service k0scontroller and starts etcd, API server, and kubelet in one process tree.
Save the public IP for subsequent scripts:
bashcurl -s https://api.ipify.org > /root/.k0s_public_ip
Start:
bashsystemctl start k0scontroller
systemctl enable k0scontroller
kubectl access
bashk0s kubectl config view --raw > /root/.kube/config
chmod 600 /root/.kube/config
From this point, kubectl and helm work normally. Subsequent scripts use k0s kubectl (bundled kubectl) to avoid requiring a separate kubectl install.
Verify cluster is ready
bashk0s status
kubectl get nodes
kubectl get pods -A
Expected output: node in Ready state, system pods in Running state within 2-3 minutes.
Common issues
Node stays NotReady: Check k0s status and journalctl -u k0scontroller -f. Common cause: br_netfilter module not loaded.
Pods stuck in ContainerCreating: overlay module missing. Run modprobe overlay and restart containerd.
High inotify errors in logs: Increase fs.inotify.max_user_watches to 1048576 for larger clusters.