To test disk speed on Linux properly, use fio. It measures random 4k IOPS (what databases need), sequential throughput (what backups and media need) and latency percentiles, and with --direct=1 it bypasses the page cache, so you measure the disk and not your RAM. dd only tells you how fast one stream of zeros is written. Here is a 60-second mixed random read/write test:
sudo apt install -y fio # Rocky, AlmaLinux: sudo dnf install -y fio
fio --name=randrw --filename=$HOME/fio.test --size=1G --direct=1 \
--ioengine=libaio --rw=randrw --bs=4k --iodepth=32 \
--runtime=60 --time_based --group_reporting
rm $HOME/fio.test # delete the test file afterwardsFor sequential throughput, use --rw=read or --rw=write with --bs=1M. Leave --ioengine=libaio in. Without it, fio uses a synchronous engine that ignores --iodepth, and you measure one request at a time while believing you tested 32.
What should I measure?
- Random 4k IOPS: input/output operations per second with small blocks at random positions. Databases, mail servers, package installs and anything with many small files depend on this number.
- Sequential throughput (MB/s): large blocks read or written in order. This decides how fast backups, video files, archives and large copies move.
- Latency, especially percentiles: how long a single request takes, including the slowest 1%. A database that commits a transaction waits for exactly that.
- Queue depth: how many requests are in flight at once.
iodepth=1is a single-threaded program waiting for each read;iodepth=32is a busy server with many clients. The same disk gives very different numbers at each, so always say which one you ran.
How do I install fio?
sudo apt install -y fio # Debian, Ubuntu
sudo dnf install -y fio # Rocky Linux, AlmaLinux, CentOS Stream
sudo apk add fio # Alpine
fio --versionfio does not need root as long as it writes to a file you own. Put the test file on the filesystem you want to measure, usually your home directory or the data directory of your application. Never put it in /tmp on systems where /tmp is a tmpfs, which Debian 13 uses by default. That is RAM, not disk; findmnt /tmp shows which kind you have.
Which fio tests should I run?
These four tests cover nearly every question about a VPS disk. The options that stay the same are kept in a variable so each test fits on one line:
F="--filename=$HOME/fio.test --size=1G --direct=1 --ioengine=libaio --runtime=60 --time_based --group_reporting"
# 1. random read IOPS (database reads, many clients)
fio --name=randread $F --rw=randread --bs=4k --iodepth=32
# 2. random write IOPS (database writes, logs, small files)
fio --name=randwrite $F --rw=randwrite --bs=4k --iodepth=32
# 3. sequential throughput (backups, media, large copies)
fio --name=seqread $F --rw=read --bs=1M --iodepth=8
fio --name=seqwrite $F --rw=write --bs=1M --iodepth=8
# 4. single-request latency (one thread waiting on each read)
fio --name=latency $F --rw=randread --bs=4k --iodepth=1
rm $HOME/fio.testBefore the first run, fio writes the 1 GB test file (“Laying out IO file”). That part is not counted. For a database-like commit test, add --fsync=1 to a random-write run with --iodepth=1: fio then flushes after every write, which is what a database does on each commit. To see how the disk behaves under several parallel jobs, add --numjobs=4; --group_reporting adds them up.
How do I read fio’s output?
A shortened result looks like this, with placeholders instead of numbers:
randread: (groupid=0, jobs=1): err= 0: pid=4182
read: IOPS=<n>, BW=<n>MiB/s (<n>MB/s)(<n>GiB/60001msec)
slat (usec): min=..., max=..., avg=..., stdev=...
clat (usec): min=..., max=..., avg=..., stdev=...
lat (usec): min=..., max=..., avg=..., stdev=...
clat percentiles (usec):
| 1.00th=[...], 5.00th=[...], 10.00th=[...], 20.00th=[...],
| 50.00th=[...], 70.00th=[...], 90.00th=[...], 95.00th=[...],
| 99.00th=[...], 99.50th=[...], 99.90th=[...], 99.95th=[...],
| 99.99th=[...]
...
Run status group 0 (all jobs):
READ: bw=<n>MiB/s (<n>MB/s), io=<n>GiB, run=60001-60001msec
Disk stats (read/write):
vda: ios=<n>/<n>, merge=0/0, ticks=<n>/<n>, in_queue=<n>, util=<n>%- IOPS and BW are the headline numbers. BW is shown in MiB/s and, in brackets, in MB/s. For 4k tests, IOPS is the figure to compare; for 1M tests, look at BW.
- clat (completion latency) is the time the storage took to complete a request. slat is the time to submit it, and lat is both together. Check the unit in the brackets: fio switches between
nsec,usecandmsec. - Percentiles: the 50th is the typical request, the 99th is the slowest 1%. Under load, users notice the 99th, not the average. A wide gap between the two means inconsistent latency.
- A sanity check: IOPS is roughly queue depth divided by average latency. At
iodepth=32and an average completion time of 1 ms, the ceiling is about 32,000 IOPS, and 4k multiplied by IOPS gives the bandwidth. If your numbers break this rule by a wide margin, something cached the I/O or the queue depth was never applied. - util in the disk stats refers to the virtual disk inside the VM. On a VPS it does not tell you how busy the physical storage is.
Why does --direct=1 matter?
Without it, Linux serves reads from the page cache in RAM and acknowledges writes as soon as they are in memory, then writes them to disk later. The result is gigabytes per second that describe your memory, not your storage. --direct=1 opens the file with O_DIRECT, which bypasses the guest’s page cache. The same reasoning applies to the file size: keep the test file larger than the RAM if you ever test without direct I/O.
Why is dd a poor benchmark?
dd writes one sequential stream with one request in flight, so it says nothing about random I/O or behaviour under load. It usually writes zeros, which some storage layers handle more cheaply than real data. Without the right flag it measures the page cache. If you only want a rough sequential figure, at least bypass the cache:
# write 1 GiB, bypassing the page cache
dd if=/dev/zero of=$HOME/dd.test bs=1M count=1024 oflag=direct status=progress
# read it back, also uncached
dd if=$HOME/dd.test of=/dev/null bs=1M iflag=direct status=progress
rm $HOME/dd.testconv=fdatasync instead of oflag=direct writes through the cache but includes the final flush in the time, which is closer to how a normal program writes a large file. Both are sequential figures only. dd prints MB/s in decimal megabytes.
How do I check disk latency with ioping?
ioping works like ping, but for storage: it sends one small request at a time and prints how long each took. It is a quick way to see whether latency is steady or jumps around:
sudo apt install -y ioping # Rocky, AlmaLinux: from EPEL
ioping -c 20 . # 20 requests against the filesystem of this directory
ioping -c 20 -D . # the same with direct I/OThe summary line ends with min/avg/max/mdev. A low average with a large mdev or an occasional high max means the storage is mostly fast but sometimes makes you wait. ioping only reads unless you add -W; do not combine that with a device path.
Is hdparm -t useful on a VPS?
Not really. hdparm -t /dev/vda reads sequentially from the device for a few seconds with a single stream. On a virtual disk that measures the virtual device and whatever caching sits behind it, and it says nothing about random I/O, writes or latency. hdparm -T measures memory. Both are fine for a physical SATA disk in your own machine and of little use on a VPS.
Why do results vary on a VPS?
A VPS disk is often not a local drive. Many hosts put VPS disks on shared, replicated network storage; VMHeaven’s Standard KVM line, for example, uses replicated enterprise storage (Ceph), where every write is stored on more than one server before it is confirmed. That trades some single-request latency for redundancy: a failed drive or host does not take your data with it. In fio terms, the iodepth=1 latency test is where replicated storage looks weakest next to a local NVMe drive, and higher queue depths, where many requests overlap, are where it does relatively better.
Results also move with the time of day and with what other tenants are doing. Instead of trusting a single run or someone else’s chart:
- Run the same recipe three times, at different times of day.
- Compare like with like: same block size, queue depth, file size, runtime and
--direct=1. A 4k QD1 number and a 4k QD32 number from two providers are not comparable. - Measure what your application does. A database server needs the random and latency tests, a backup target the sequential ones.
- Keep tests short. A minute per recipe is enough; hours of saturating writes on shared storage help nobody, including you.
Is high iowait a sign of a slow disk?
Often, yes, and it is usually why people start benchmarking. A high wa value in top means the CPU is idle while processes wait for storage. Before you blame the disk, look at what is waiting and how long each request takes:
sudo apt install -y sysstat # provides iostat and pidstat
iostat -x 1 # per device: r/s, w/s, r_await, w_await (ms), aqu-sz, %util
pidstat -d 1 # per process: kB read and written per second
vmstat 1 # 'b' = processes blocked on I/O, 'wa' = iowaitIf r_await or w_await climbs while your application is busy, storage is the bottleneck. Fix the workload first: missing database indexes, a buffer pool that is too small, or logging every request synchronously. More RAM for the page cache is often the cheapest way to cut reads. If wa is low and us is high, the disk is not the problem; see VPS high CPU usage. Workloads bound to one or two fast cores are what the Hi-CPU KVM line is built for. If the disk is simply full rather than slow, go to No space left on device. If the disk tests fine but transfers are still slow, measure the network instead: how to test a VPS’s network.
How do I test disk speed on Windows?
On a Windows server, Microsoft’s free DiskSpd is the fio equivalent, and CrystalDiskMark puts a graphical front end on the same kind of test. Its RND4K Q32T1 row corresponds to the 4k random tests above at iodepth=32, and SEQ1M to the sequential ones. The DiskSpd command below runs 60 seconds of 4k random I/O, 30% writes, at queue depth 32 with caching disabled and latency statistics on:
diskspd.exe -c1G -d60 -r -w30 -b4K -t1 -o32 -Sh -L C:\diskspd.dat
del C:\diskspd.datFrequently asked
What is a good IOPS number for a VPS?
There is no single good number, because it depends on block size, queue depth and the workload. Measure what your application needs instead: run 'iostat -x 1' at peak load and look at r/s and w/s, then run fio with 4k blocks at the same queue depth on the server you are considering. If the measured IOPS are comfortably above your peak and the 99th-percentile latency stays low, the disk is good enough for that workload. Compare providers only with identical fio settings.
Should I use dd or fio to test disk speed?
Use fio. dd writes one sequential stream with one request in flight, usually of zeros, and without oflag=direct it measures the page cache rather than the disk. fio tests random and sequential I/O at any queue depth, bypasses the cache with --direct=1 and reports latency percentiles. dd with oflag=direct is fine for a rough sequential figure and nothing more.
Can fio damage my disk?
It won't harm the hardware, but it can destroy data. A write test pointed at a device such as /dev/vda or at an existing file overwrites what is there. fio refuses to write to a mounted device by default, but not to an unmounted disk or a file. Always use a new test file in your home or data directory, delete it afterwards, and keep runs to a minute or so on shared storage.
Why is the second disk benchmark run faster?
Usually caching. Without --direct=1, data from the first run is still in the page cache, so the second run reads from RAM. Storage layers below the VM can cache recently used blocks too, and on thin-provisioned storage the first write to a new block costs more than later writes. Use --direct=1, a file larger than the cache, and several runs at different times before drawing conclusions.