The I/O scheduler decides in what order the kernel sends read and write requests to a disk. With spinning hard drives, reordering requests mattered a lot. With SSDs, NVMe and virtual disks, the best scheduler is often the simplest one. Here’s how to check yours, and when to change it.
Check the current scheduler
lsblk -d -o NAME,ROTA,SCHED
cat /sys/block/vda/queue/scheduler
# [none] mq-deadline kyber bfq ← brackets mark the active one
ROTA=1 means a rotational disk (HDD), and 0 an SSD or NVMe. Virtual disks may report either.
The schedulers (multi-queue era)
| Scheduler | How it works | Best for |
|---|---|---|
| none | No reordering; requests go straight to the device | NVMe, fast SSDs, virtual disks (the host schedules) |
| mq-deadline | Groups requests, with deadlines so none starve | SATA SSDs, HDDs, general servers |
| bfq | Fair share per process; good interactivity | Desktops, HDDs, slow devices; more CPU overhead |
| kyber | Latency targets for reads and writes | Fast devices with mixed workloads |
The old single-queue schedulers (cfq, deadline, noop) were removed in kernel 5.0. On older guides, read noop as none, and deadline as mq-deadline.
On a VPS
Inside a KVM virtual machine, the guest’s disk (/dev/vda with virtio) sits on top of the host’s storage stack, which has its own scheduling. Reordering requests twice rarely helps, so none (or mq-deadline) is usually the right choice, and most distributions already pick a sensible default for virtio disks. See what is KVM virtualization.
Don’t expect big gains from changing the scheduler on a VPS. Disk performance there depends mostly on the underlying storage, and on your I/O pattern. Measure before and after.
Change it temporarily
echo mq-deadline | sudo tee /sys/block/vda/queue/scheduler
cat /sys/block/vda/queue/scheduler
The change lasts until reboot.
Make it persistent with udev
sudo tee /etc/udev/rules.d/60-iosched.rules <<'EOF'
# virtio and NVMe: none
ACTION=="add|change", KERNEL=="vd[a-z]|nvme[0-9]n[0-9]", ATTR{queue/scheduler}="none"
# SATA/SAS SSDs: mq-deadline
ACTION=="add|change", KERNEL=="sd[a-z]", ATTR{queue/rotational}=="0", ATTR{queue/scheduler}="mq-deadline"
# HDDs: bfq
ACTION=="add|change", KERNEL=="sd[a-z]", ATTR{queue/rotational}=="1", ATTR{queue/scheduler}="bfq"
EOF
sudo udevadm control --reload && sudo udevadm trigger
Measure, don’t guess
Use a benchmark that matches your workload (random 4K reads and writes for databases, sequential for backups and media):
fio --name=randrw --filename=/var/tmp/fio.test --size=1G --rw=randrw --bs=4k \
--iodepth=32 --numjobs=4 --direct=1 --runtime=60 --time_based --group_reporting
Compare IOPS and latency percentiles between schedulers. See fio disk benchmark.
For live behaviour: iostat -x 1 (await, %util), and biolatency from eBPF tools. See Linux disk performance and eBPF basics.
Other queue settings (advanced)
cat /sys/block/vda/queue/nr_requests # queue depth
cat /sys/block/vda/queue/read_ahead_kb # read-ahead (raise for sequential reads)
Raising read-ahead can help streaming workloads (backups, media), and hurt random-access databases. Change one thing at a time, and measure.
When the scheduler actually matters
On dedicated servers with spinning disks, the scheduler can make a visible difference: bfq keeps an interactive shell responsive while a backup hammers the disk, and mq-deadline keeps database latency predictable under mixed load. On NVMe and virtual disks, the device or the host is far better at ordering requests than the guest kernel, so the scheduler mostly adds CPU overhead. If you’re chasing disk latency on a VPS, look first at what’s doing the I/O (iotop, biosnoop), at swap activity, and at database settings.
Quick reference
| Device | Usually best |
|---|---|
VPS virtio disk (vda) |
none or mq-deadline |
| NVMe | none |
| SATA SSD | mq-deadline (or none) |
| HDD | bfq or mq-deadline |
| Check | cat /sys/block/<dev>/queue/scheduler |
| Persist | udev rule in /etc/udev/rules.d/ |
Related: Linux I/O wait and ext4 vs XFS.