Skip to content
Go back

CPU and I/O Priority with nice and ionice

By SumGuy 4 min read
CPU and I/O Priority with nice and ionice

You start a backup. Server crawls. Your monitoring dashboard turns red. Everyone’s cursing your name in Slack.

Sounds familiar? The problem isn’t that backups can’t run—it’s that they’re running at the same priority as everything else. You need to tell Linux: “Hey, this backup is important, but not more important than serving requests.”

That’s what nice and ionice do. They’re your priority knobs.

Nice: CPU Priority

nice ranges from -20 (highest priority) to 19 (lowest priority). Default is 0.

Think of it like airline boarding. -20 is first class. 19 is standby.

Terminal window
$ nice -n 10 backup.sh

This runs backup.sh at nice level 10—lower priority than normal. If the CPU is busy, other tasks get more cycles.

Terminal window
$ nice -n -5 critical-job.sh

This runs at -5 (higher priority). It’ll preempt lower-priority work.

Only root can go negative. Non-root users can only increase their own niceness (move toward 19).

Renice a Running Process

Started a job at priority 0 but realize it’s killing production? Adjust it on the fly:

Terminal window
$ pgrep -f backup.sh
12345
$ renice -n 10 -p 12345
12345 (process ID) old priority 0, new priority 10

Check it:

Terminal window
$ ps aux | grep backup.sh
user 12345 0.0 15.2 ... 10 ? Sl 12:34 5:20 backup.sh
^
that's the NI column

Ionice: I/O Priority

CPU nice affects scheduling. But here’s the kicker: I/O is separate. A process can be low CPU priority and still hammer your disk, blocking everyone.

That’s where ionice comes in. It sets I/O scheduling class:

Terminal window
$ ionice -c idle -p 12345

Classes are:

Terminal window
# Run an rsync backup at idle I/O priority
$ ionice -c idle rsync -av /home /backup/
# Combine nice + ionice: low CPU, idle I/O
$ nice -n 15 ionice -c idle backup.sh

For best-effort, you can specify a priority (0-7):

Terminal window
$ ionice -c best-effort -n 5 my-process

Real-World Example

Your nightly database dump is tanking the web server. Fix it:

backup-db.sh
#!/bin/bash
# Start at low priority
exec nice -n 10 ionice -c idle mysqldump --single-transaction --all-databases | gzip > /backup/db-$(date +%s).sql.gz

Or apply it to an already-running process:

Terminal window
$ pgrep mysqldump
5678
$ renice -n 15 -p 5678
$ ionice -c idle -p 5678
# Verify
$ ps aux | grep mysqldump | grep -v grep
$ ionice -p 5678
idle

Watch It In Action

Before priority tuning:

Terminal window
$ iostat -x 1
...
util: 95% disk is saturated

After:

Terminal window
# Terminal 1: Run heavy I/O at idle priority
$ ionice -c idle dd if=/dev/zero of=/tmp/test.bin bs=1M count=1000
# Terminal 2: Check I/O still responsive
$ time find / -type f > /dev/null
real 0m2.341s still fast!

Without ionice, that find would crawl.

Caveats

ionice needs CFQ scheduler. Modern kernels use mq-deadline or bfq. Check:

Terminal window
$ cat /sys/block/sda/queue/scheduler
[mq-deadline] kyber bfq none

If you don’t see cfq, ionice still works but behaves differently. BFQ respects nice more consistently.

Real-world: System load matters. If the server is idle, nice/ionice do nothing. A backup at priority 19 will still zip through if there’s no contention.

Docker/cgroups override this. Containers have their own resource limits. Nice is per-process; cgroups are per-container.

Systemd and Priority

If you’re running a service through systemd, set nice and I/O priority in the unit file instead of a shell wrapper:

/etc/systemd/system/backup.service
[Service]
Nice=10
IOSchedulingClass=idle
ExecStart=/usr/local/bin/backup.sh

Reload and restart:

Terminal window
sudo systemctl daemon-reload
sudo systemctl restart backup

This is cleaner than wrapping commands in nice ... ionice ... in a shell script, and it applies every time the service starts — even after reboots.

Verify What’s Actually Set

Check the current nice/ionice of any running process:

Terminal window
# Nice value (NI column)
ps -o pid,ni,comm -p $(pgrep backup.sh)
# ionice class
ionice -p $(pgrep backup.sh)

A nice value of 10 means you set it correctly. A class of idle means your I/O prioritization is active. If you see the default (0 and none), your changes didn’t take effect.

When to Use Them

Your 2 AM self—the one paged because backups hammered the DB—will be grateful you did this.


Share this post on:

Send a Webmention

Written about this post on your own site? Send a webmention and it may appear here.


Previous Post
Docker Exit Codes: Why Your Container Keeps Restarting
Next Post
The .dockerignore File You're Not Writing

Related Posts