Verify your VPS disk is really NVMe on Linux
Your host says NVMe. Check it from inside the guest with lsblk, sysfs, nvme-cli and a bounded fio latency run, and learn why virtio hides the disk.
Verify an NVMe disk on Linux in four steps
To verify an NVMe disk on a Linux VPS, run four checks in order: lsblk for the device name, the rotational flag in sysfs for spinning media, nvme list for a real NVMe controller, and a short fio run for the one number a host cannot dress up. The first three take a second each. The last one settles the question, because on most VPS platforms the hypervisor hides the physical disk from the guest.
NVMe (non-volatile memory express) is the protocol flash storage speaks over PCIe (peripheral component interconnect express) lanes. It replaced the SATA and AHCI path that was designed for spinning drives, and it is fast because it removes a queueing bottleneck between the CPU and the flash. Inside a virtual machine you rarely speak that protocol at all. You speak to a virtual disk the host assembles for you, so the device name in your guest describes a driver, not the media in the rack.
Step 1: what does lsblk show?
lsblk reads the kernel's list of block devices. The -d flag hides partitions, so you see one line per disk.
lsblk -d -o NAME,ROTA,SIZE,TYPE,TRAN,MODELA KVM VPS usually answers like this:
NAME ROTA SIZE TYPE TRAN MODEL
vda 0 80G diskThree naming patterns cover almost every Linux server:
nvme0n1is namespace 1 on NVMe controller 0. Your guest is driving an NVMe device, real or emulated.sdais the SCSI layer. Real SATA and SAS disks land here, and so does the virtio-scsi driver.vdais virtio-blk, the paravirtual block driver most KVM hosts use by default.
The TRAN (transport) column reads nvme for an NVMe device and sata for a SATA disk the guest can see directly. It is usually blank under virtio-blk, because there is no physical transport for the guest to report. MODEL is blank for the same reason. An empty model string on a VPS is normal and tells you nothing about the hardware.
Step 2: rule out a spinning disk
DISK=$(lsblk -dno NAME,TYPE | awk '$2 == "disk" { print $1; exit }')
echo "checking $DISK"
cat "/sys/block/$DISK/queue/rotational"
readlink -f "/sys/block/$DISK"The rotational file holds 1 when the kernel believes the device has spinning platters and 0 for everything else. On bare metal the value comes from the drive. Under virtio-blk it comes from a feature bit the host sets, so a 0 rules out a plain hard drive and proves nothing beyond that. Read it anyway: a 1 on a plan sold as solid state storage is a flat contradiction, and it is the one screenshot support cannot argue with.
readlink -f resolves the sysfs symlink and prints the bus the device hangs off:
/sys/devices/pci0000:00/0000:00:05.0/virtio2/block/vdaThe word virtio in that path is your answer for this step. An NVMe device gives a path containing nvme, such as /sys/devices/pci0000:00/0000:01:00.0/nvme/nvme0/nvme0n1, and a directly attached ATA disk gives one containing ata1. This is the same fact lsblk printed, taken from the kernel rather than from a formatted column, which is useful when the TRAN field is empty.
Step 3: ask nvme-cli and the PCI bus
sudo apt update
sudo apt install -y nvme-cli pciutils
sudo nvme list || echo "nvme-cli found no NVMe device"
lspci | grep -i -e nvme -e 'non-volatile' || echo "no NVMe controller on this guest's PCI bus"nvme list prints one row per namespace, with the controller's serial number, model string and firmware revision. An empty table means no NVMe device is exposed to your guest. lspci printing no matching line says the same thing from the other side: the virtual PCI bus your guest can see carries no NVMe controller. Both empty results are the normal outcome on a virtio VPS, and neither one is evidence that the host has no NVMe drives.
If a namespace does appear, nvme-cli goes further. sudo nvme id-ctrl /dev/nvme0 prints the controller identity, and sudo nvme smart-log /dev/nvme0n1 prints temperature, power-on hours and the percentage of rated write endurance used. Run those only when nvme list actually listed a device, since both commands need a real /dev/nvme* node to open.
Why a genuinely NVMe host still shows /dev/vda
The hypervisor decides which device model your guest sees, and that choice is independent of the media underneath. Three arrangements are common.
- virtio-blk or virtio-scsi over a file, a logical volume, or a ZFS dataset that lives on NVMe drives. You see
vdaorsda. The storage is NVMe. The guest has no way to learn that. - An emulated NVMe controller in front of any storage at all. You see
nvme0n1even when the bytes land on a SATA array or on a network volume two racks away. - PCIe passthrough, where the host hands a physical controller to one guest. You see a real
nvme0n1with a real model string. This is rare on shared VPS plans, because the card is then dedicated to that one customer.
So the device name can mislead you in both directions. Worse, the host can put a write-back cache, a RAID layer, or a replicated network volume between your writes and the flash, and each of those changes the performance you get without changing a single name in /sys. That is why the name is where you start and not where you stop. If you are still choosing a plan, the difference between NVMe and SATA SSD storage explains what each tier is actually worth paying for.
The honest test is latency, so measure it
fio (flexible I/O tester) issues real reads against a real file and reports how long each one took. Small random reads are the right workload here, because they expose the round trip to the media and cannot be served by read-ahead.
First create the test file and prove that this filesystem accepts unbuffered I/O. --direct=1 opens the file with O_DIRECT, which bypasses the page cache. Without it you measure your own RAM and get numbers no disk can produce.
sudo apt install -y fio
fio --name=prep --filename=/var/tmp/nvme-check.tmp --size=256M --bs=1M --rw=write --direct=1 --end_fsync=1 > /dev/null \
&& echo "unbuffered writes work here, the timing test is valid" \
|| echo "this filesystem refuses direct=1, so the timing test below will not run"If that printed the refusal, the path you chose sits on a filesystem without O_DIRECT support. Container overlay filesystems and some network filesystems behave this way. Pick a directory on the VPS root filesystem and try again. If it printed success, run the measurement:
fio --name=randread4k --filename=/var/tmp/nvme-check.tmp --bs=4k --rw=randread \
--direct=1 --iodepth=1 --numjobs=1 --runtime=20 --time_based --group_reporting \
|| echo "fio stopped early, read its first line for the reason"
rm -f /var/tmp/nvme-check.tmpThe run is bounded on purpose: one job, queue depth 1, twenty seconds, 256 MB of data. It will not fill your disk and it will not get you flagged for abuse. Queue depth 1 is also the honest setting for this question, because deep queues let a slow device hide behind parallelism while latency stays high.
Reading the fio output
Two lines matter. The summary line looks like read: IOPS=9012, BW=35.2MiB/s, and under it fio prints a clat block. clat is completion latency: the time between fio submitting a read and the kernel handing back the data. Read the avg value, then read the 99.00th percentile in the clat percentiles list. The average tells you the storage class. The 99th percentile tells you how often a neighbour on the same host makes you wait.
Fio prints latency in microseconds when values are small and switches to milliseconds when they are not. Check the unit in the line before you compare anything.
What numbers separate NVMe class from SATA class
The figures below are typical published values for single-job 4k random reads at queue depth 1, collected from vendor documentation and community benchmarks as of August 2026. They are bands to compare against, not measurements from your server.
The data behind this chart
[
{
"label": "Local NVMe",
"avg_latency_us": 110
},
{
"label": "Local SATA SSD",
"avg_latency_us": 320
},
{
"label": "Network block storage",
"avg_latency_us": 900
}
]The data behind this chart
[
{
"label": "Local NVMe",
"iops": "9,000"
},
{
"label": "Local SATA SSD",
"iops": "3,100"
},
{
"label": "Network block storage",
"iops": "1,100"
}
]A local NVMe volume answers one 4k random read in roughly 110 microseconds and reaches about 9,000 IOPS at queue depth 1. A local SATA SSD sits near 320 microseconds and 3,100 IOPS. Network attached block storage lands around 900 microseconds and 1,100 IOPS, because every single read crosses a network hop before any flash is touched.
At queue depth 1 those two columns are the same fact stated twice: one read at a time means throughput is simply one divided by latency. If your average is closer to 110 than to 320 microseconds, you are on NVMe class storage whatever lsblk decided to call the device. If it is closer to 900 microseconds, something slower than local flash is in the path, and the word NVMe on the order page is describing the host's drives rather than your volume.
Run it more than once before you complain
One run is a sample, not a result. A VPS shares its disks, so a busy neighbour can double your latency for ten minutes and then disappear. Some platforms also grant burst credits that make the first minutes of any test look excellent. Run the same command three or four times across different hours of the day and compare the worst result, not the best one. A single bad run is weather. A pattern is a fault worth a support ticket, and the ticket lands better with three timestamped fio outputs attached. For a wider picture that covers CPU and network as well as disk, a full VPS benchmark run uses the same discipline on the other subsystems.
What to do with the answer
If the latency is in the NVMe band, stop worrying about the device name and move on. vda is not a downgrade. It is the fastest virtual disk driver most hosts offer, and it is what you want.
If the latency is in the network storage band on a plan sold as local NVMe, you have a specific, reproducible claim: the exact fio command, the average completion latency, and the times you ran it. That is a support ticket rather than a forum argument. Before you send it, confirm the disk is not simply full or heavily fragmented, and confirm nothing on the box is writing hard while you test.
Run this check the day you provision, not the day something feels slow, so you have a baseline to compare against later. It fits naturally into the first ten minutes on a new VPS, next to setting up the firewall and the SSH keys. If the whole distinction between storage tiers is still fuzzy, what an SSD VPS actually gives you covers the ground underneath this one.
FAQ
Why does lsblk show /dev/vda when my host advertises NVMe?
Because vda names the virtio-blk driver in your guest, not the hardware on the host. A KVM hypervisor presents a paravirtual block device backed by a file, a logical volume or a dataset, and that backing store can sit on NVMe drives without the guest ever learning it. The name describes the virtualisation layer. Only a latency measurement describes the media.
Does rotational 0 prove I have an NVMe disk?
No. /sys/block/<dev>/queue/rotational holding 0 means the kernel does not believe the device has spinning platters, and under virtio that value is set by a feature bit the host chooses. It rules out an ordinary hard drive. It cannot tell NVMe from SATA SSD, and it cannot tell local flash from a network volume. A 1 is still worth acting on, since it contradicts any plan sold as solid state storage.
Why is nvme list empty on my VPS?
Because no NVMe controller is exposed to your guest. nvme list and lspci both read what the virtual machine can see, and a virtio-blk or virtio-scsi disk presents no NVMe controller to enumerate. An empty table is the normal result on most VPS plans and is not proof that the host lacks NVMe drives. Install nvme-cli with sudo apt install -y nvme-cli and expect the empty table unless a controller was passed through.
What fio result counts as NVMe class storage?
At queue depth 1 with 4k random reads and --direct=1, an average completion latency near 110 microseconds is NVMe class, and roughly 9,000 IOPS follows from it. Around 320 microseconds points at a SATA SSD, and around 900 microseconds points at network attached storage, where each read crosses a network hop. Those are typical published bands for August 2026, so compare orders of magnitude rather than exact figures, and repeat the run at different hours before drawing a conclusion.