SSD Nodes Learn 8GB RAM — $66/yr
Guides Matt ConnorBy Matt Connor

NVMe vs SSD VPS: does it matter?

NVMe beats SATA SSD on IOPS and latency, but on a VPS the hypervisor and your neighbours decide what you get. Measure your own with fio.

Does NVMe matter on a VPS?

NVMe matters on a VPS when your software sends many small reads and writes and waits for each one to finish. It changes very little for a site that serves cached pages, or for a program that spends its time waiting on the network. The medium is one factor. The hypervisor in front of the disk, and the other guests sharing the same host, set the ceiling you actually get.

What NVMe changes, and what it does not

NVMe (non-volatile memory express) is not a kind of flash memory. It is the protocol and the connection used to reach the flash. An NVMe device sits on PCIe (peripheral component interconnect express) lanes and speaks NVMe. A SATA (serial ATA) SSD sits on a SATA link and speaks AHCI (advanced host controller interface). The memory chips holding your bytes can be identical in both.

Two things differ, and both are about the command path rather than the storage itself.

Queues. AHCI gives the kernel one command queue that holds 32 commands. NVMe allows thousands of queues, in practice one per CPU core, each far deeper than 32. One process reading one block at a time cannot see that difference. A database with 64 reads outstanding can: on SATA the 33rd request waits for a queue slot before the device even sees it, while the NVMe device accepts them all and works on them together.

Link width. A SATA III link runs at 6 Gbit/s, which is about 550 MB/s of real data after protocol overhead. That is a fixed ceiling, whatever flash sits behind it. Four PCIe lanes carry several gigabytes per second, so the link stops being the limit.

Latency is where expectations are usually wrong. At queue depth 1, meaning a single request in flight, a SATA SSD answers a 4k read in about 100 to 150 microseconds. NVMe answers in about 80 to 100. Both are fast, and nothing you run will notice the difference on one request. The gap opens under concurrency. Queue depth, the number of requests in flight at once, is the setting that decides whether the two media look alike or very different.

Network block storage is a third class with different physics. A write crosses a network to a storage cluster and is acknowledged only once the cluster holds it, so its latency is measured in milliseconds instead of microseconds. What you buy for that latency is durability: the volume outlives the host it is attached to, and it can be snapshotted and resized.

Typical published figures: NVMe, SATA SSD and network storage

ChartTypical published figures: 4k random read, queue depth 32
The data behind this chart
[
  {
    "disk": "Local NVMe SSD",
    "iops_4k_read": "184,000",
    "p99_latency_ms": 0.4,
    "seq_read_mbps": "3,400"
  },
  {
    "disk": "Local SATA SSD",
    "iops_4k_read": "90,000",
    "p99_latency_ms": 1.2,
    "seq_read_mbps": "550"
  },
  {
    "disk": "Network block storage",
    "iops_4k_read": "12,500",
    "p99_latency_ms": 6.5,
    "seq_read_mbps": "250"
  }
]

A local NVMe device is commonly quoted at 184,000 random 4k read IOPS (input/output operations per second) at queue depth 32. The same test on a SATA SSD is quoted near 90,000, held down by the single AHCI queue and the 6 Gbit/s link. Network block storage is usually capped by the provider rather than by the hardware, and 12,500 is a common documented ceiling.

Latency says the same thing in the unit your users feel. The p99 read latency, meaning the slowest 1 percent of requests, is around 0.4 ms on local NVMe and 1.2 ms on SATA. Put a network in the path and it becomes 6.5 ms, more than ten times the NVMe figure.

Sequential reads are the largest gap and the least useful one: 3,400 MB/s against 550 MB/s. Almost nothing on a server reads one large file end to end at full speed. The random column and the latency column describe what a database, a mail queue or a package manager really does.

Where these figures come from, and why yours will differ

The 3 rows are vendor datasheet figures for the local devices and documented per-volume limits for network storage, current as of July 2026 and rounded. They assume a 4k block size, random reads, queue depth 32 and a single job, which is the shape of test a vendor publishes. Your VPS is a guest on a shared host, so the same test on your box normally returns less, and it varies between runs. Read these rows as the shape of the difference between the three classes, not as a target to hit.

Which workloads notice the disk

One rule explains all of them: a workload notices the disk only when it waits for the disk. Linux keeps recently used file data in RAM, in the page cache, so the second read of a file never reaches storage. If the working set, meaning the data actually in use, fits in RAM, reads become memory reads after the first pass. Writes are different. Any write the application flushes with fsync() must be on stable storage before the application is allowed to continue.

Work that commits. PostgreSQL, MySQL and SQLite call fsync() or fdatasync() on commit, and each commit waits for the device to answer. The commit rate of one connection is therefore set by write latency, not by bandwidth. A device that flushes in 0.2 ms allows far more commits per second than one that takes 5 ms, and no amount of throughput changes that. MySQL says so in the error log when the flush cannot keep up:

[Note] InnoDB: page_cleaner: 1000ms intended loop took 4589ms. The settings might not be optimal.

PostgreSQL reports it in its checkpoint lines, where a large sync= value means the flush itself was slow:

LOG:  checkpoint complete: wrote 8192 buffers (25.0%); write=27.694 s, sync=11.207 s, total=39.001 s

Work that touches many small files. Every file carries metadata operations that one large sequential read does not. npm install, git clone of a big repository, unpacking container images, a Maildir mail store and a backup walking a large tree all spend their time on small random access. A restic backup job on a VPS reads and hashes every file it has not seen before, so the wall-clock time of a backup over a million files tracks random read latency closely. The same is true of du -sh, which reads metadata and nothing else.

Databases that outgrow RAM belong here too. Once the index no longer fits in the page cache, every lookup becomes a random read, and the disk is back in the critical path.

Which workloads do not notice the disk

A blog or a small company site. The pages are small, the page cache holds all of them after the first request, and the limit is CPU for rendering or bandwidth for assets. A LAMP stack on Ubuntu 24.04 serving a low-traffic site does almost no disk IO once it is warm.

Media streaming. One 4K stream at 40 Mbit/s reads 5 MB/s. Ten of them read 50 MB/s, which even network block storage serves without effort. A Jellyfin media server on a VPS is limited by your network egress allowance, and by CPU when it transcodes, not by the storage medium.

Local model inference. Running Ollama on a VPS to self-host an LLM reads the model file once, then works in RAM. NVMe cuts the load time of a 20 GB model from minutes to seconds. It does not change tokens per second, which is bound by memory bandwidth and CPU.

Anything waiting on an external service. A worker that spends 800 ms per job on an HTTP request will not go faster on a better disk.

Why the hypervisor matters as much as the medium

You never talk to the device. You talk to a virtual disk that the hypervisor presents, usually through virtio, and several decisions in that layer matter more than NVMe against SATA.

You cannot see the medium from inside the guest. lsblk -d -o NAME,ROTA,SIZE,MODEL shows vda with an empty model, because virtio does not pass the drive identity through. cat /sys/block/vda/queue/rotational reports what the hypervisor advertises, so a 0 there is not proof of flash. nvme list, from the nvme-cli package, lists nothing on most VPS even when the host is full of NVMe drives, because your disk is a virtio device and not an NVMe device. A plan that says NVMe is usually describing what the host contains. Your volume may still be network attached.

Host cache mode moves the numbers more than the medium does. With writeback caching on the host, a guest fsync() can return as soon as the host has the data in its own RAM. That produces a benchmark result no physical device could deliver. It also means a host crash can lose writes your database believes are safe. With cache mode none, the numbers are lower and honest.

Caps and burst credits. Many providers cap IOPS per volume or per plan, and many network volumes use a burst allowance. A burst allowance is a pool of credits: the volume runs fast while credits last, then drops to a much lower baseline. The symptom is easy to recognise. An import or a restore runs quickly for several minutes, then slows down sharply and stays slow, with nothing changed in your configuration. You spent the credits.

Neighbours. On a shared host, your disk latency changes with what other guests are doing. This is the reason to measure more than once. Run the same test in the morning and again in the evening, and compare the spread. On a busy host, the difference between two runs on the same volume is often larger than the published difference between two media.

How to measure the disk your VPS actually has

Install fio, the standard IO benchmark, and measure. Three cautions first. The test creates a file, so it uses disk space and it counts against any IOPS allowance you are billed for. Keep the runs short. Do not run it at full queue depth against a volume that is serving live traffic, because you will be competing with your own application.

sudo apt update && sudo apt install -y fio ioping sysstat
cd /var/tmp

Random read at queue depth 32, which is the depth vendors quote:

fio --name=randread --filename=fio.test --size=1G --bs=4k --rw=randread \
  --ioengine=libaio --direct=1 --iodepth=32 --numjobs=1 \
  --runtime=30 --time_based --group_reporting

The line that matters starts with read:

  read: IOPS=184k, BW=719MiB/s (754MB/s)(21.1GiB/30001msec)

--direct=1 bypasses the guest page cache, so the result describes the device instead of your RAM. Leave it out and you measure memory, which returns a number no disk can reach. Use --size=4G or larger if you have the space, because a 1G file can sit entirely inside the host's cache and flatter the result.

Queue depth 1 shows raw latency, which is what a single-threaded process feels:

fio --name=lat --filename=fio.test --size=1G --bs=4k --rw=randread \
  --ioengine=libaio --direct=1 --iodepth=1 --runtime=30 --time_based

The commit test predicts database behaviour. It writes 4k and calls fdatasync() after every write, so the reported rate includes the flush:

fio --name=commit --filename=fio.test --size=1G --bs=4k --rw=randwrite \
  --ioengine=psync --fdatasync=1 --runtime=30 --time_based
rm -f fio.test

The IOPS figure from that run is close to the highest number of small transactions per second one database connection can commit, because a commit waits for the same flush.

For a quick sample without fio:

ioping -c 20 .
--- . (ext4 /dev/vda1) ioping statistics ---
19 requests completed in 4.13 ms, 76 KiB read, 4.60 k iops, 17.9 MiB/s
min/avg/max/mdev = 174.2 us / 217.6 us / 386.1 us / 51.3 us

The mdev value, the mean deviation, is worth as much as the average. A large deviation on an idle box means the storage backend is shared and busy.

How to read the result

As of July 2026, these are reasonable readings for a small VPS. Tens of thousands of 4k random read IOPS at queue depth 32, with a queue depth 1 latency under about 0.3 ms, is consistent with local flash. A queue depth 1 latency of several milliseconds means a network path, whatever the plan is called. Sequential reads that stop near 550 MB/s are the signature of a SATA link. A number far above what any single device can do means caching is in the path, almost always on the host.

To see what your live workload is doing to the disk:

iostat -x 1 3
vmstat 1 5
cat /proc/pressure/io

In iostat -x output, read r_await and w_await, the average milliseconds a request waited, and aqu-sz, the average queue length. Ignore %util on a virtual disk. It reports the share of time at least one request was outstanding, which says nothing about saturation on a device that serves many requests at once, so %util of 100 alongside an r_await of 0.2 ms is a healthy busy disk. In vmstat, the wa column is the percentage of CPU time spent waiting on IO. If /proc/pressure/io exists on your kernel, its some avg10= value is the share of the last 10 seconds in which at least one task was stalled on IO, which is the most direct answer to the question of whether storage is your bottleneck.

What a disk-bound VPS looks like

A high load average with idle CPU and a large wa in vmstat means processes are queued behind the disk. The clearest kernel signal is this message in dmesg -T:

INFO: task jbd2/vda1-8:194 blocked for more than 120 seconds.

That line appears because a kernel thread waited over two minutes for storage to answer, so the hung task watchdog logged it. jbd2 is the ext4 journal thread, which means the whole filesystem was waiting, not one badly behaved program. On a VPS it usually points at the storage backend or at an exhausted IOPS allowance.

The application symptoms follow the same pattern. Median response time stays acceptable while the slowest requests grow a long tail, because only the requests that hit the disk pay. apt upgrade sits at Unpacking for minutes, because dpkg flushes as it writes. git status in a large repository takes seconds. These are metadata and flush costs, so more bandwidth would not help.

What to do when the disk is the limit

Buy RAM before you buy IOPS. If the working set fits in the page cache, reads stop reaching the disk at all. Doubling memory often beats moving to a faster storage class, and it usually costs less.

Reduce the number of flushes, where the data allows it. In PostgreSQL, synchronous_commit = off lets a commit return before the write is on disk. You can lose the last fraction of a second of transactions if the server dies. The database is not corrupted, because the write-ahead log is still written in order. That trade is right for an analytics copy and wrong for payments. innodb_flush_log_at_trx_commit = 2 in MySQL is the same trade.

Batch small files. A transfer or backup of a million small files is dominated by per-file cost, so archiving first and moving one stream is faster on high-latency storage than copying the tree file by file.

Keep discard working on thin volumes. On thin provisioned storage the backend does not know a block is free until the filesystem says so, and a volume that never trims slowly loses write performance. Ubuntu ships a weekly timer for this:

systemctl status fstrim.timer
sudo fstrim -av

fstrim -av prints the bytes trimmed per mount point. A message that the discard operation is not supported means the virtual disk does not pass discard through to the host, so there is nothing for you to fix.

Skip the IO scheduler tuning. On a virtio disk, cat /sys/block/vda/queue/scheduler usually shows none already, and the real scheduling happens on the host, where you have no access. Skip noatime as well: Ubuntu mounts with relatime by default, which already avoids nearly every atime write.

Choosing a plan

Pay for NVMe when a database, a mail server, a CI runner or a package-heavy build lives on the box. Do not pay a premium for a cached website or for an app whose time goes to external calls. If you are unsure, the disk is probably not your limit, because most small VPS workloads run out of RAM or bandwidth first.

Measure on day one, while you are working through the first ten minutes on a new VPS, and keep the output in a file. A baseline is how you later prove that the host got slower rather than your code. Prefer providers that state the storage class and any IOPS cap in writing. If a plan says NVMe and a queue depth 1 read takes 4 ms, you have network storage on an NVMe-equipped host. That is a fair thing to sell, and a different thing to buy.

FAQ

Is NVMe always faster than a SATA SSD on a VPS?

No. At queue depth 1 the two are close, roughly 80 to 150 microseconds for a 4k read, and a single-threaded program cannot tell them apart. NVMe pulls ahead when many requests are in flight, because AHCI offers one queue 32 commands deep while NVMe offers thousands of deeper queues. On a shared host, load from other guests can move your latency more than the medium does, so measure your own volume with fio instead of reading the plan name.

How do I check whether my VPS really uses NVMe?

You cannot check it directly, because virtio hides the physical device. lsblk shows vda with no model string, nvme list returns nothing, and /sys/block/vda/queue/rotational reports only what the hypervisor advertises. Measure behaviour instead. A queue depth 1 random 4k read under about 0.3 ms means local flash. Several milliseconds means a network hop is in the path. Sequential reads that stop near 550 MB/s indicate a SATA link.

Does NVMe make my website load faster?

Usually not. After the first request, Linux serves the files from the page cache in RAM, so the disk goes idle. Page speed on a small VPS is normally bound by application CPU time and by bandwidth. The disk returns to the critical path if the site writes on every request, for example a database-backed cart that commits often, because each commit waits for a flush to complete.

What is a good fio result for a VPS?

As of July 2026, a small VPS on local flash typically returns tens of thousands of 4k random read IOPS at queue depth 32, with a queue depth 1 latency under 0.3 ms. Network block storage typically returns a few thousand IOPS at a latency of a few milliseconds. Run the test three times at different hours. A wide spread between runs tells you more than the average does, because it shows how much the other guests on the host affect you.

Should I put my database on network block storage?

You can, and many managed services do, but the commit path pays for it. Every flush crosses the network, so a single connection commits fewer small transactions per second than it would on local flash. You get durability that survives the host in exchange. If you choose network storage for a write-heavy database, group work into larger transactions so that fewer flushes carry more rows.

#nvme#ssd#storage#performance#benchmarking