The drive is stuttering under load
You run a large archive extraction or compile a project and your desktop freezes for three seconds. The mouse cursor lags. The terminal stops echoing keystrokes. You check iostat and see the disk queue depth spiking while throughput barely moves. The storage hardware is fine. The kernel is just sending requests in the wrong order.
What the I/O scheduler actually does
The I/O scheduler sits between the filesystem layer and the block device driver. It decides the order, batching, and priority of read and write operations. Modern Fedora uses the multi-queue block layer, or blk-mq. The old single-queue schedulers like cfq and noop are gone. The kernel now ships with mq-deadline, bfq, and none.
The scheduler reorders requests to minimize head movement on spinning platters or to optimize parallel command submission on flash memory. A mismatched scheduler forces the drive to work against its physical design. When the scheduler merges adjacent requests, it reduces the number of hardware commands. When it batches writes, it smooths out latency spikes. When it prioritizes reads over writes, it keeps interactive applications responsive during background backups.
The blk-mq subsystem changed how the kernel talks to storage. Instead of one global queue, each CPU core gets its own hardware queue. The scheduler now runs per-queue. This reduces lock contention and scales with modern multi-core systems. It also means the scheduler choices matter less than they did in the 2010s. The kernel default is usually adequate. You only change it when you measure a specific bottleneck.
Run iostat -x 1 to watch queue depth and await latency before you change anything. Guessing the scheduler wastes time.
Check what your system is already using
The sysfs interface exposes the active scheduler for every block device. The path follows the device name. NVMe drives use nvme0n1, nvme1n1, and so on. SATA and SAS drives use sda, sdb, etc.
cat /sys/block/sda/queue/scheduler
# The active scheduler is wrapped in brackets.
# The other available schedulers appear in parentheses.
# Replace sda with your actual device name like nvme0n1.
The output shows exactly what the kernel loaded at boot. If you see [mq-deadline] bfq none, the system is already using mq-deadline. The kernel picks the default based on the device type. NVMe devices usually default to none. SATA SSDs default to mq-deadline. Spinning disks default to bfq or mq-deadline depending on the kernel version.
Check the scheduler before you change it. Half the time the symptom is gone after a reboot.
Change the scheduler temporarily
Changing the scheduler on the fly requires root privileges. The sysfs path reflects the exact block device. Use tee to avoid shell redirection permission errors. The change applies immediately to all pending I/O operations.
echo mq-deadline | sudo tee /sys/block/nvme0n1/queue/scheduler
# Writes the scheduler name directly to the kernel interface.
# sudo tee avoids "Permission denied" from shell redirection.
# The change applies immediately to all pending I/O operations.
echo bfq | sudo tee /sys/block/sda/queue/scheduler
# Targets a traditional spinning hard drive.
# bfq optimizes for fairness and rotational latency.
# Rebooting will reset this value to the kernel default.
The temporary change is useful for testing. Run a benchmark or reproduce the stutter. Compare iostat output before and after. If latency drops and throughput holds steady, the scheduler change is worth persisting. If performance degrades, revert it immediately.
Test in a VM or on a non-critical drive first. A botched scheduler change can freeze a production database.
Make the change survive a reboot
The sysfs tree rebuilds on every boot. You need a persistent mechanism. The standard approach uses the iosched mount option in /etc/fstab. This ties the scheduler to the filesystem mount rather than the raw block device. It survives kernel updates and device name changes.
# /etc/fstab entry example
UUID=your-uuid-here /home ext4 defaults,iosched=bfq 0 2
# iosched=bfq tells the mount process to set the scheduler.
# The option applies when the filesystem is mounted at boot.
# Use blkid to find the correct UUID for your partition.
The iosched option only works for filesystems that support it. Btrfs and XFS ignore it silently. You must use a systemd unit or a udev rule for those filesystems. If you prefer a systemd unit, create a drop-in that runs after the device is ready. Place it in /etc/systemd/system/. Never edit files in /usr/lib/systemd/system/. Those ship with packages and get overwritten on updates.
# /etc/systemd/system/set-io-scheduler.service
[Unit]
Description=Set I/O scheduler for /dev/sda
After=local-fs-pre.target
# Runs after early filesystem preparation.
# Ensures the block device exists before writing to sysfs.
[Service]
Type=oneshot
ExecStart=/usr/bin/echo bfq > /sys/block/sda/queue/scheduler
# Writes the scheduler name directly.
# Type=oneshot means systemd considers it done after execution.
[Install]
WantedBy=multi-user.target
# Activates the service during normal boot.
# Use systemctl enable set-io-scheduler.service to activate it.
Run systemctl daemon-reload after creating the unit. Enable it with systemctl enable set-io-scheduler.service. The service runs before most user services start. Trust the package manager. Manual file edits drift, snapshots stay.
Verify the new scheduler is active
Reboot the system. Check the scheduler again after login.
cat /sys/block/sda/queue/scheduler
# Confirms the persistent configuration survived the reboot.
# The bracketed name should match your fstab or systemd unit.
# If it reverted, check journalctl -u set-io-scheduler.service.
If the systemd unit failed, the journal shows the exact failure point. Use journalctl -xeu set-io-scheduler.service to read the logs. The x flag adds explanatory text and the e flag jumps to the end. Most sysadmins type journalctl -xeu <unit> muscle-memory style.
Run systemctl status set-io-scheduler.service to see recent log lines AND state in one view. Always check status before restart.
Common pitfalls and error patterns
The iosched mount option only works for filesystems that support it. Btrfs and XFS ignore it silently. You must use a systemd unit or a udev rule for those filesystems. If you see mount: /home: mount failed: Invalid argument, your fstab syntax is wrong. Check for missing commas or typos in the options field.
Another common issue involves NVMe devices. NVMe drives use multiple hardware queues. The none scheduler often performs best because the drive firmware already handles request reordering. Forcing bfq on an NVMe drive adds software overhead without physical benefit. You will see higher latency in iostat output.
If the system drops to an emergency shell, check the boot logs. A misconfigured fstab entry with an invalid scheduler name will prevent the root filesystem from mounting. The kernel prints VFS: Cannot open root device "UUID=..." or unknown-block(0,0). Boot from a live USB, mount the root partition, and fix the fstab entry before rebooting.
SELinux denials show up in journalctl -t setroubleshoot with a one-line summary. Read those before disabling SELinux. A custom systemd unit that writes to sysfs usually works out of the box, but a restrictive policy can block it. Check the audit log if the service fails silently.
Run journalctl -xe first. Read the actual error before guessing.
Pick the right scheduler for your hardware
Use mq-deadline when you run a database or a high-throughput web server on SSD storage. Use bfq when you share a spinning hard drive among multiple users or run virtual machines that compete for disk access. Use none when your NVMe drive firmware already handles command reordering and you want zero software overhead. Stick with the kernel default when you run a standard desktop workload and do not measure I/O latency.
The scheduler is a tuning knob, not a magic fix. Profile your I/O pattern first. Change one variable at a time. Reboot before you debug. Half the time the symptom is gone.