Skip to content

TutorialsContainers & Docker

How to install K3s on Ubuntu: single-node Kubernetes with Traefik and HTTPS

Install K3s on Ubuntu with the official script, use kubectl as your admin user, publish a test app through Traefik with Let's Encrypt, then back up and upgrade.

  • Intermediate
  • 45 min read
  • Updated

Tested on: Ubuntu 24.04 LTS, Ubuntu 26.04 LTS

On this page
  1. Prerequisites
  2. Step 1 — Open only the ports K3s needs
  3. Step 2 — Download and review the install script
  4. Step 3 — Install K3s
  5. Step 4 — Use kubectl as your admin user
  6. Step 5 — Deploy a test app behind Traefik
  7. Step 6 — Add HTTPS with cert-manager and Let's Encrypt
  8. Back up and restore
  9. Upgrade K3s
  10. Uninstall K3s
  11. Troubleshooting
  12. Traefik's svclb pods stay Pending and ports 80 and 443 do not answer
  13. Pods cannot resolve names or reach services while ufw is on
  14. The connection to the server 127.0.0.1:6443 was refused
  15. kubectl says you must be logged in to the server
  16. The certificate never becomes ready
  17. Next steps

K3s is a lightweight, fully conformant Kubernetes distribution packaged as a single binary. It bundles containerd, the Flannel network, CoreDNS, a local-path storage provisioner, the ServiceLB load balancer and the Traefik ingress controller, so one server is enough to run a real Kubernetes cluster. This guide installs K3s on an Ubuntu LTS server with the official install script (downloaded and read first), sets up kubectl for your admin user, publishes a test app through Traefik, adds Let's Encrypt certificates with cert-manager, and covers backups, upgrades and uninstalling.

Prerequisites

  • A server running Ubuntu 26.04 LTS or Ubuntu 24.04 LTS. SUSE's K3s support matrix for v1.36 validates Ubuntu 26.04, 24.04 and 22.04. Debian is not in that matrix; the K3s documentation says K3s should work on most modern Linux systems and lists setup notes for Debian, but this guide only claims the validated Ubuntu releases.
  • A non-root user with sudo rights and SSH key login, as in Secure a new Linux server and Set up SSH keys.
  • A unique hostname for the server (K3s requires one per node).
  • Ports 80 and 443 free. Traefik takes them through ServiceLB, so do not run Caddy, Nginx or another web server on the same host.
  • For the HTTPS part: a domain such as hello.example.com with an A record pointing at the server.
  • You do not need Docker: K3s brings its own container runtime.
ResourceMinimum (official)Suggested starting point
CPU2 cores per server node2 vCPU to learn, 4 vCPU for real workloads
Memory2 GB per server node4 GB or more
DiskSSD recommended; the datastore is write-intensive40 GB SSD

The minimums come from the K3s requirements page. The right-hand column is a conservative starting point, not an official or benchmarked figure; add what your workloads need on top.

Step 1 — Open only the ports K3s needs

For a single node, the internet only needs to reach SSH and Traefik on ports 80 and 443. The other ports in the K3s requirements (6443 for agents, 8472/udp for Flannel VXLAN, 10250 for the kubelet, 2379-2380 for etcd) carry traffic between nodes. The K3s documentation recommends turning ufw off, and gives rules for when you keep it on. This guide keeps it on with those rules: the two from lines allow the default pod network (10.42.0.0/16) and service network (10.43.0.0/16).

Bash
sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw allow from 10.42.0.0/16 to any
sudo ufw allow from 10.43.0.0/16 to any
sudo ufw enable
sudo ufw status verbose

If you want to run kubectl from your own computer without an SSH tunnel, also allow the API port from your address only (replace the documentation IP with yours):

Bash
sudo ufw allow from 198.51.100.25 to any port 6443 proto tcp

Step 2 — Download and review the install script

The official installation method is the script at get.k3s.io. Download it first so you can read what it will do as root:

Bash
curl -sfL https://get.k3s.io -o k3s-install.sh
less k3s-install.sh

In short, the script:

  • detects systemd (or OpenRC) and decides whether to install a server or, when K3S_URL is set, an agent;
  • looks up the version on the stable release channel at update.k3s.io unless you set INSTALL_K3S_VERSION or INSTALL_K3S_CHANNEL;
  • downloads the k3s binary from the project's GitHub releases, verifies its SHA-256 checksum and installs it to /usr/local/bin/k3s;
  • creates kubectl, crictl and ctr symlinks when those commands do not exist yet, plus k3s-killall.sh and k3s-uninstall.sh;
  • writes the k3s systemd unit and its environment file under /etc/systemd/system/, then enables and starts the service.

Step 3 — Install K3s

Run the script you just reviewed:

Bash
sudo sh k3s-install.sh

Check the service and the cluster:

Bash
sudo systemctl status k3s --no-pager
sudo kubectl get nodes
sudo kubectl get pods -A

The node is listed as Ready after a minute or so. In kube-system you should see coredns, local-path-provisioner, metrics-server, traefik and an svclb-traefik pod Running, and the helm-install jobs Completed.

Step 4 — Use kubectl as your admin user

K3s writes the admin kubeconfig to /etc/rancher/k3s/k3s.yaml, which only root can read. It grants full cluster-admin rights, so give your own user a private copy instead of making the original file world-readable:

Bash
mkdir -p ~/.kube
sudo cp /etc/rancher/k3s/k3s.yaml ~/.kube/config
sudo chown $USER:$USER ~/.kube/config
chmod 600 ~/.kube/config
echo 'export KUBECONFIG=$HOME/.kube/config' >> ~/.bashrc
export KUBECONFIG=$HOME/.kube/config
kubectl get nodes

kubectl get nodes now works without sudo. K3s refreshes the certificates inside k3s.yaml every time it starts, but copies are not updated; if kubectl ever reports that you are unauthorized, copy the file again.

To use the cluster from your own computer, copy ~/.kube/config there. The simplest secure path is an SSH tunnel, which keeps the server: https://127.0.0.1:6443 line valid:

Bash
ssh -L 6443:127.0.0.1:6443 user@203.0.113.10

If you opened port 6443 for your IP in Step 1 instead, replace 127.0.0.1 in the server field with the server's address.

Step 5 — Deploy a test app behind Traefik

K3s deploys Traefik with an IngressClass. Confirm its name:

Bash
kubectl get ingressclass

You should see traefik. Now create a manifest with a namespace, an Nginx deployment, a service and an ingress for hello.example.com. Open a new file with nano hello.yaml and paste:

YAML
apiVersion: v1
kind: Namespace
metadata:
  name: hello
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: hello
  namespace: hello
spec:
  replicas: 1
  selector:
    matchLabels:
      app: hello
  template:
    metadata:
      labels:
        app: hello
    spec:
      containers:
        - name: web
          image: nginx:stable
          ports:
            - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: hello
  namespace: hello
spec:
  selector:
    app: hello
  ports:
    - port: 80
      targetPort: 80
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: hello
  namespace: hello
spec:
  ingressClassName: traefik
  rules:
    - host: hello.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: hello
                port:
                  number: 80

Apply it and test, first on the server itself with a Host header, then through DNS:

Bash
kubectl apply -f hello.yaml
kubectl -n hello get pods,svc,ingress
curl -I -H "Host: hello.example.com" http://127.0.0.1
curl -I http://hello.example.com

Both requests return HTTP/1.1 200 OK with Server: nginx. Traffic flows from ServiceLB on port 80 to Traefik, then to the hello service.

Step 6 — Add HTTPS with cert-manager and Let's Encrypt

Traefik can terminate TLS, but it needs certificates. cert-manager is the common way to obtain and renew them inside Kubernetes. Install it with the static manifest from the cert-manager documentation (v1.21.2 is the release the install page shows in October 2026; check it for a newer one):

Bash
kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.21.2/cert-manager.yaml
kubectl -n cert-manager wait --for=condition=Available deployment --all --timeout=300s
kubectl -n cert-manager get pods

The cert-manager, cert-manager-cainjector and cert-manager-webhook pods should be Running. Next, create a ClusterIssuer that solves Let's Encrypt HTTP-01 challenges through Traefik. Save it as clusterissuer.yaml with your own email address:

YAML
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-prod
spec:
  acme:
    server: https://acme-v02.api.letsencrypt.org/directory
    email: [email protected]
    privateKeySecretRef:
      name: letsencrypt-prod-account-key
    solvers:
      - http01:
          ingress:
            ingressClassName: traefik
Bash
kubectl apply -f clusterissuer.yaml
kubectl get clusterissuer

READY turns True once cert-manager has registered the ACME account. Now ask for a certificate by adding an annotation and a tls section to the ingress. Save this as hello-ingress.yaml:

YAML
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: hello
  namespace: hello
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
  ingressClassName: traefik
  tls:
    - hosts:
        - hello.example.com
      secretName: hello-tls
  rules:
    - host: hello.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: hello
                port:
                  number: 80
Bash
kubectl apply -f hello-ingress.yaml
kubectl -n hello get certificate
curl -I https://hello.example.com

After a minute the certificate shows READY True and curl succeeds over HTTPS.

Back up and restore

A single K3s server uses an embedded SQLite datastore by default. Its state lives in /var/lib/rancher/k3s/server/db/, and the server token at /var/lib/rancher/k3s/server/token encrypts confidential data in it; without the same token a restored datastore is unusable. Stop K3s for a consistent copy (running pods are not drained, but the API is unavailable for a moment), archive both together with /etc/rancher/k3s, and start it again:

Bash
sudo mkdir -p /opt/backups
sudo systemctl stop k3s
sudo tar czf /opt/backups/k3s-server-$(date +%F).tar.gz /var/lib/rancher/k3s/server/db /var/lib/rancher/k3s/server/token /etc/rancher/k3s
sudo systemctl start k3s

This archive holds the cluster state, not your application data. Persistent volumes from the default local-path StorageClass are stored under /var/lib/rancher/k3s/storage; back up databases inside them with their own dump tools. Keep your manifests (hello.yaml and so on) in Git, and copy every backup off the server.

To restore on the same server, install the same K3s version with INSTALL_K3S_VERSION, then replace the datastore and token.

Bash
sudo systemctl stop k3s
sudo rm -rf /var/lib/rancher/k3s/server/db
sudo tar xzf /opt/backups/k3s-server-2026-10-09.tar.gz -C /
sudo systemctl start k3s

If you installed with --cluster-init, K3s uses embedded etcd instead of SQLite. It then takes snapshots automatically at 00:00 and 12:00 and keeps five in /var/lib/rancher/k3s/server/db/snapshots. You can take one on demand:

Bash
sudo k3s etcd-snapshot save
sudo ls /var/lib/rancher/k3s/server/db/snapshots

To restore an etcd snapshot, stop K3s and run sudo k3s server --cluster-reset --cluster-reset-restore-path= followed by the snapshot path, as described in the K3s etcd-snapshot documentation.

Upgrade K3s

K3s publishes release channels: stable (the default and recommended for production), latest, and one channel per Kubernetes minor version such as v1.36. To upgrade, download the script again and re-run it with the same environment variables and flags you used for the installation. Options stored in /etc/rancher/k3s/config.yaml are kept, but anything you passed on the command line and leave out now is lost.

Bash
curl -sfL https://get.k3s.io -o k3s-install.sh
sudo INSTALL_K3S_CHANNEL=stable sh k3s-install.sh
kubectl get nodes

The VERSION column shows the new release. Do not skip Kubernetes minor versions: if stable is more than one minor version ahead of you, upgrade through the version channels one step at a time, for example with INSTALL_K3S_CHANNEL=v1.36. Read the release notes first and take a backup. For hands-off upgrades, K3s documents the system-upgrade-controller, which applies upgrade Plans that follow a channel.

Uninstall K3s

Bash
sudo /usr/local/bin/k3s-uninstall.sh

Troubleshooting

Traefik's svclb pods stay Pending and ports 80 and 443 do not answer

ServiceLB publishes Traefik on host ports 80 and 443, and its pods only start on nodes where those ports are free. Another web server (Caddy, Nginx, Apache) or a container from Docker is probably holding them. Check with sudo ss -tlnp | grep -E ':(80|443) ', stop the other service and wait for the pods to start.

Pods cannot resolve names or reach services while ufw is on

The firewall blocks traffic from the pod and service networks. Add the two from 10.42.0.0/16 and from 10.43.0.0/16 rules from Step 1, or follow the K3s recommendation to turn ufw off and use your provider's network firewall instead.

The connection to the server 127.0.0.1:6443 was refused

The K3s service is not running or is still starting. Check sudo systemctl status k3s and read the log with sudo journalctl -u k3s -e. A common cause is a wrong option in /etc/rancher/k3s/config.yaml.

kubectl says you must be logged in to the server

Your copy of the kubeconfig contains certificates that K3s has since renewed. Repeat the copy commands from Step 4.

The certificate never becomes ready

Run kubectl -n hello describe certificate hello-tls and kubectl get challenges -A. Most failures are DNS records that do not point at the server yet, or port 80 being blocked, because Let's Encrypt's HTTP-01 check must reach Traefik over plain HTTP.

Next steps

Frequently asked questions

Do I need Docker to run K3s?

No. K3s ships its own containerd runtime together with the crictl and ctr tools. Docker can run on the same server, but keep ports 80 and 443 free for the bundled Traefik ingress controller.

Is a single-node K3s cluster enough for production?

A single K3s server is a complete Kubernetes cluster and works well for small workloads, but it is a single point of failure. For redundancy, K3s supports additional server nodes with embedded etcd or an external datastore.

Which ports does a single-node K3s server need?

From the internet only SSH plus 80 and 443 for Traefik. Port 6443 is the Kubernetes API; open it only to your own IP address if you run kubectl remotely. The other ports in the K3s documentation carry traffic between nodes.

Where is the K3s kubeconfig file?

K3s writes the admin kubeconfig to /etc/rancher/k3s/k3s.yaml, readable by root. Copy it to ~/.kube/config for your admin user and copy it again after K3s renews its certificates, because copies are not updated automatically.

Can I turn off the bundled Traefik?

Yes. Start every server with --disable=traefik, for example as a disable entry in /etc/rancher/k3s/config.yaml, and install the ingress controller you prefer instead.

Generate Password

Please confirm