How to Install Longhorn on K3s with Dedicated NVMe Storage

Updated on Sep 7, 2026
Mila H
10 MINS READ
Table of Contents
Longhorn K3s Setup with NVMe

Longhorn is a free and open-source storage system built for Kubernetes. It turns normal disks on your servers into safe and replicated storage that your pods can use. This guide provides a full Longhorn K3s setup NVMe build, from a clean multi-node cluster to a working backup target.

Why Use Longhorn with Dedicated NVMe Disks

A proper Longhorn K3s setup NVMe build uses separate physical NVMe drives just for storage, so your operating system disk stays untouched and fast.

NVMe drives give much lower latency than normal SSDs or spinning disks. Since Longhorn writes data to multiple replicas across nodes, disk speed matters a lot. Slow disks make every write wait longer.

If you are picking new hardware, servers like PerLod dedicated servers come with NVMe drives and private networking, which is exactly what replicated Kubernetes storage needs for fast and safe replica traffic between nodes.

What You Need Before You Start

For the Longhorn K3s setup NVMe build, make sure you have:

  • At least 3 physical or virtual servers. 1 master node and 2+ worker nodes. Longhorn needs 3 nodes to keep 3 replicas of data.
  • Ubuntu 24.04 LTS or newer on every node.
  • A separate and unused NVMe disk on each storage node. For example, /dev/nvme1n1, not the OS disk.
  • Root or sudo access on every node.
  • Private networking between nodes for fast replica sync, with a public IP only on the node you will manage from.
  • At least 4 GB RAM and 2 CPU cores per node.

Step 1: Prepare Your NVMe Disks on Each Node

The first step is to find and check your extra NVMe disk on every worker node. Do not format your OS disk.

Bash
lsblk

Look for a disk with no mount point, such as nvme1n1. Format it with the ext4 filesystem, which Longhorn supports well:

Bash
sudo mkfs.ext4 /dev/nvme1n1sudo mkdir -p /mnt/longhorn-nvmesudo mount /dev/nvme1n1 /mnt/longhorn-nvme

You must make the mount permanent by adding it to /etc/fstab. First, get the disk UUID:

Bash
sudo blkid /dev/nvme1n1

Then add the following line with your UUID to /etc/fstab:

Bash
echo 'UUID=your-disk-uuid /mnt/longhorn-nvme ext4 defaults 0 2' | sudo tee -a /etc/fstab

Reload systemd's view of mounts and verify the entry mounts correctly:

Bash
sudo systemctl daemon-reloadsudo mount -adf -h /mnt/longhorn-nvme

Repeat this on every node that will provide storage. This dedicated disk step is what makes a Longhorn K3s setup NVMe deployment behave like a real production cluster.

Step 2: Install Required Packages on Every Node

Longhorn needs open-iscsi and nfs-common on every node, including the master node. These packages let Longhorn attach volumes and support backup mounts. Install them with the commands below:

Bash
sudo apt updatesudo apt install open-iscsi nfs-common -ysudo systemctl enable --now iscsidsudo systemctl status iscsid

Make sure iscsid is active and running. If your servers use multipath, add Longhorn's disks to its blacklist. If you skip this, multipath can grab the disks and cause attach errors on bare-metal servers.

Step 3: Install K3s on the Master Node

On your master node, use the command below to install K3s using the official install script. This installs the current stable K3s release automatically:

Bash
curl -sfL https://get.k3s.io | sh -

Then, run the following command to verify it is running:

Bash
sudo k3s kubectl get nodes

Get the node token, which the other nodes need to join the cluster:

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

Also, note your master node's private IP address, using ip a.

Step 4: Join Worker Nodes to the Cluster

On each worker node, run the K3s agent install command and replace the IP and token with your own values from Step 3:

Bash
curl -sfL https://get.k3s.io | K3S_URL=https://<master-node-private-ip>:6443 \K3S_TOKEN=<your-node-token> sh -

Then, from the master node, confirm all nodes joined and are Ready:

Bash
sudo k3s kubectl get nodes -o wide

You should see one master node and your worker nodes, all marked Ready. You need this multi-node setup for a real Longhorn K3s setup NVMe test, because a single-node cluster cannot show true replica failover.

Tips: Teams that run virtual machines on the same cluster can check this guide on running KubeVirt VMs on K3s. It shows how to set up VM and container workloads on the same nodes.

Step 5: Install Longhorn K3s Setup NVMe with Helm

Now install Longhorn itself using Helm, which is the most reliable install method. First, install Helm if you do not have it yet:

Bash
curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash

Set your kubeconfig so helm and kubectl can talk to your K3s cluster:

Bash
export KUBECONFIG=/etc/rancher/k3s/k3s.yaml

Then, add the Longhorn Helm repository and update it:

Bash
helm repo add longhorn https://charts.longhorn.iohelm repo update

Install the current stable Longhorn release into its own namespace:

Bash
helm install longhorn longhorn/longhorn \  --namespace longhorn-system \  --create-namespace \  --version 1.12.1 \  --set defaultSettings.defaultReplicaCount=3

This installs Longhorn, the current stable release at the time of writing. Watch the pods start up:

Bash
kubectl -n longhorn-system get pod -w

Wait until all pods show Running. This can take a few minutes on the first install since Longhorn pulls several container images to every node.

Step 6: Open the Longhorn Dashboard

Longhorn has its own web dashboard. K3s does not set up a load balancer on its own, so use kubectl port-forward to open the dashboard from your system:

Bash
kubectl -n longhorn-system port-forward svc/longhorn-frontend 8080:80

Open http://localhost:8080 in your browser. You should see the Longhorn dashboard listing your nodes and their disks.

For real production use, put this behind Nginx or Caddy with authentication, or an Ingress with TLS.

Step 7: Add and Tag Your Dedicated NVMe Disks

By default, Longhorn tries to use free space on the root disk of each node, which you do not want. Go to the Longhorn UI, click Node, select a worker node, and click Edit Node and Disks.

Remove the default disk if it points at the root filesystem. Then click Add Disk and point it at your NVMe mount path:

  • Path: /mnt/longhorn-nvme
  • Add a disk tag: nvme

Repeat this for every storage node. Adding disk tags is what lets you later force specific volumes to only use NVMe disks.

Also, you can do this with kubectl instead of the UI:

Bash
kubectl -n longhorn-system patch nodes.longhorn.io worker-01 --type=merge -p '{  "spec": {    "disks": {      "nvme-disk-1": {        "path": "/mnt/longhorn-nvme",        "allowScheduling": true,        "tags": ["nvme"]      }    }  }}'

While you are in the Node view, add a node tag like "storage" to each node that should hold Longhorn replicas. This helps if some nodes are only for running apps, not for storing data.

Step 8: Create a Custom StorageClass with Replica Count and Tags

The default Longhorn StorageClass works, but for real use you should make your own. This lets you control how many replicas you get and which disks are used. Create the file on your master node with this command:

Bash
cat <<EOF > longhorn-nvme-sc.yamlapiVersion: storage.k8s.io/v1kind: StorageClassmetadata:  name: longhorn-nvmeprovisioner: driver.longhorn.ioallowVolumeExpansion: truereclaimPolicy: DeletevolumeBindingMode: Immediateparameters:  numberOfReplicas: "3"  staleReplicaTimeout: "480"  diskSelector: "nvme"  nodeSelector: "storage"  fsType: "ext4"EOF

Then, apply it with the command below:

Bash
kubectl apply -f longhorn-nvme-sc.yaml
  • numberOfReplicas: "3" keeps three copies of every volume, spread across three different nodes.
  • diskSelector: "nvme" forces replicas onto disks tagged nvme only.
  • nodeSelector: "storage" forces replicas onto nodes tagged storage only.

This combination is the core of a correct Longhorn K3s setup NVMe design. Fast disks, spread-out copies, and no accidental use of slow root-disk space.

Step 9: Create a Test Volume and Check It Works

At this point, you can create a small test app with a PersistentVolumeClaim using your new StorageClass. Create the file with this command:

Bash
cat <<EOF > test-pvc.yamlapiVersion: v1kind: PersistentVolumeClaimmetadata:  name: nvme-test-pvcspec:  accessModes:    - ReadWriteOnce  storageClassName: longhorn-nvme  resources:    requests:      storage: 5Gi---apiVersion: v1kind: Podmetadata:  name: nvme-test-podspec:  containers:    - name: writer      image: busybox      command: ["sh", "-c", "echo hello-longhorn > /data/test.txt && sleep 3600"]      volumeMounts:        - mountPath: /data          name: nvme-storage  volumes:    - name: nvme-storage      persistentVolumeClaim:        claimName: nvme-test-pvcEOF

Apply it and check the volume is Bound and the pod is Running:

Bash
kubectl apply -f test-pvc.yamlkubectl get pvckubectl get pod nvme-test-pod

In the Longhorn UI, open Volume, find your new volume, and confirm it shows 3 healthy replicas on 3 different nodes.

Step 10: Test Real Node Failover

Pick the node currently running nvme-test-pod. Then, act like it failed by turning it off or by draining it:

Bash
kubectl drain <node-name> --ignore-daemonsets --delete-emptydir-data

Kubernetes will reschedule nvme-test-pod onto a healthy node. Longhorn already kept a full copy of the data on another node. So when the pod comes back, its data is still there:

Bash
kubectl exec nvme-test-pod -- cat /data/test.txt

You should still see hello-longhorn. This confirms your replicas are truly independent copies, not just a single disk pretending to be redundant. Once you are done testing, bring the node back:

Bash
kubectl uncordon <node-name>

Step 11: Configure an S3-Compatible Backup Target

Replicas protect you from a node failing. Backups protect you from a whole cluster failing, a bad kubectl delete, or ransomware. Longhorn supports AWS S3, MinIO, Backblaze, and other S3-compatible storage as a backup target.

First, create a Kubernetes secret with your S3 credentials:

Bash
kubectl create secret generic longhorn-backup-secret \  --namespace longhorn-system \  --from-literal=AWS_ACCESS_KEY_ID="your-access-key-id" \  --from-literal=AWS_SECRET_ACCESS_KEY="your-secret-access-key" \  --from-literal=AWS_ENDPOINTS="https://s3.your-provider.com"

Leave AWS_ENDPOINTS empty if you are using real AWS S3. Set it to your provider's URL if you use MinIO, Backblaze, or another S3-compatible service.

Now point Longhorn at your bucket with the command below:

Bash
kubectl patch settings.longhorn.io backup-target \  -n longhorn-system \  --type merge \  -p '{"value": "s3://your-bucket-name@your-region/"}' kubectl patch settings.longhorn.io backup-target-credential-secret \  -n longhorn-system \  --type merge \  -p '{"value": "longhorn-backup-secret"}'

Make sure the S3 URL ends with a /, or Longhorn will show a connection error. You can check the result in the Longhorn UI under Settings > General > Backup Target.

Step 12: Take and Verify a Backup

Go to the Longhorn UI, open Volume, select your test volume, and click Create Backup. Once it finishes, open Backup in the left menu. You should see your backup listed with a timestamp and size.

To automate this instead of clicking manually, you can create a recurring backup job. Create the file with this command:

Bash
cat <<EOF > longhorn-daily-backup.yamlapiVersion: longhorn.io/v1beta2kind: RecurringJobmetadata:  name: daily-backup  namespace: longhorn-systemspec:  cron: "0 2 * * *"  task: "backup"  groups:    - default  retain: 7  concurrency: 2EOF

Apply it with the command below:

Bash
kubectl apply -f longhorn-daily-backup.yaml

This runs a backup every night at 2 AM and keeps the last 7 copies, which gives you a real recovery point if something goes wrong.

Best Practices for Production Clusters

  • Always use at least 3 storage nodes so numberOfReplicas: 3 can actually spread data across separate hardware.
  • Keep the Longhorn NVMe disks separate from the OS disk, so a full OS disk never blocks storage writes.
  • Set CPU and memory requests on longhorn-manager and engine-image pods on busy clusters, so they are not starved by other workloads.
  • Test failover and backup restore regularly, not just once during setup. A backup you have never restored is not a proven backup.
  • Use private networking between nodes for replica traffic, which lowers latency and keeps storage traffic off your public network.

Conclusion

You now have a full Longhorn K3s NVMe cluster setup. Including multiple nodes, tagged NVMe disks, a custom StorageClass with 3 replicas, a tested node failure, and auto backups to S3. This is how real production storage runs on bare metal, not just a single-node demo.

Next, you can add more storage nodes, change replica counts, or send backups to a disaster-recovery cluster elsewhere.

We hope you enjoy this guide. For more detailed information, you can check the Official Longhorn Documentation.