Process Priority: nice, renice, and Sharing CPU Time Fairly
A typical server has far more runnable processes than it has CPU cores at any given instant — a web server, a database, a backup script, and a monitoring agent all want CPU time simultaneously, but each core can execute exactly one instruction stream at a time. The kernel's scheduler decides, many times per second, which runnable process gets the core next and for how long. Most of the time the default scheduling policy is fine and invisible; the moments it matters are exactly the ones covered here — when a background batch job should not be allowed to starve an interactive service, or when a critical process needs to be protected from ever waiting behind less important work.
This article covers how nice values bias the scheduler's decisions, how to set and change them with nice and renice, and — because nice alone does not solve every priority problem — how real-time scheduling policies and I/O priority (ionice) fit into the same picture.
Nice Values: A Bias, Not a Guarantee
Linux's default scheduler for ordinary processes (the Completely Fair Scheduler, CFS, on most kernels in current use) does not give every process an identical, fixed time slice. Instead, each process has a nice value, ranging from -20 (highest scheduling priority) to 19 (lowest), and the scheduler uses it to weight how much CPU time a process is entitled to relative to others competing for the same core. A default, unmodified process runs at nice value 0.
The name is intentionally descriptive: raising a process's nice value makes it "nicer" to everyone else — it voluntarily accepts a smaller share of CPU time so other processes can run more.
NI is the nice value you set; PRI is the actual, kernel-computed scheduling priority the nice value feeds into (higher PRI here means scheduled sooner — the exact numeric convention for PRI differs from NI's, which is a frequent source of confusion; always reason in terms of NI, not PRI, when adjusting anything).
Warning
A nice value is a relative bias, not a guaranteed share and not a hard limit. If a niced-down process (NI 19) is the only runnable process on an otherwise idle core, it still gets 100% of that core — nice values only matter when multiple processes are actually competing for the same CPU at the same moment. Setting a generous nice value does not throttle a process's CPU usage on an idle system.
Starting a Process at a Chosen Priority: nice
-n 15 starts backup-script.sh at nice value 15 — a low-priority background job that yields CPU time readily to anything more important competing for the same core. Without an explicit level, nice alone applies a default offset (usually 10) to whatever the shell's own nice value currently is.
Raising priority above the default (0) requires elevated privilege, because an unprivileged process being allowed to grab priority freely over everything else would be a straightforward way to starve the rest of the system:
An ordinary user attempting a negative nice value without sudo gets an error or a silently clamped value, depending on the shell and system configuration — always verify with ps -o ni afterward rather than assuming the requested value took effect.
Changing a Running Process's Priority: renice
nice only applies at the moment a process starts. To adjust a process that is already running, use renice:
-p targets a PID directly. renice also accepts -u <user> to retarget every process owned by a given user, and -g <group> for a process group — useful for deprioritizing an entire batch job's worth of child processes in one command rather than hunting down each PID individually.
As with nice, lowering a nice value (making a process more favored) below its current level, or below 0 at all, requires the privilege to do so — an unprivileged user can only raise their own process's nice value (make it less favored), never lower it, which prevents a user from ever un-deprioritizing a process an administrator intentionally throttled.
Real-Time Scheduling: When nice Is Not Enough
Nice values only adjust priority within the normal, "best-effort" scheduling class. For a process that genuinely must preempt everything else — a low-latency audio pipeline, certain industrial control software — Linux offers real-time scheduling policies (SCHED_FIFO, SCHED_RR) that sit in an entirely separate, higher-priority class above all normal nice-value-governed processes:
chrt -f sets SCHED_FIFO (first-in-first-out among processes of the same real-time priority, running until it blocks or yields) at real-time priority 50 (real-time priorities range 1–99, with 99 the highest — a fixed, separate numeric range from ordinary nice values).
Danger
Real-time scheduling is a fundamentally different tool from nice, with a much larger blast radius if misused. A SCHED_FIFO process that never voluntarily blocks or yields can starve every ordinary process on the system, including critical kernel housekeeping threads running at normal priority, potentially making the machine unresponsive to the point that only a hardware reset recovers it. Never set a real-time policy on a general-purpose service without first testing on a disposable machine, and always confirm the specific workload actually calls sched_yield() or blocks periodically rather than running an unbounded tight loop.
CPU Priority Is Not I/O Priority: ionice
A process with a favorable nice value can still be delayed badly if it is waiting on slow disk I/O, because nice values only govern CPU scheduling, not the order the kernel services block I/O requests in. ionice addresses that separate axis, when the underlying I/O scheduler supports it (commonly with cfq or bfq; less effective under none/mq-deadline, common defaults for NVMe devices):
-c 3 sets the idle I/O class — this process's I/O requests are only serviced when no other process needs the disk at all, ideal for a background backup or indexing job that should have zero impact on foreground disk-bound work. -c 2 (best-effort, the default) accepts a priority level 0–7 after it; -c 1 (real-time) is reserved for privileged, latency-critical I/O and carries the same starvation risk as real-time CPU scheduling if misapplied broadly.
Combining both in one line — lowest CPU priority and idle I/O class — is the standard pattern for a backup job that should be functionally invisible to everything else running on the machine.
A Realistic Scenario: A Nightly Backup Slowing Down the Web Server
Problem. Every night at 02:00, a tar-based backup job runs, and monitoring shows request latency on the web server spiking during that window. You need to reduce the backup's impact without delaying or breaking the backup itself.
Confirm the backup is actually the cause, before changing anything:
Checking top's %Cpu(s) line during the backup window (as covered in Process Monitoring) — if wa (I/O wait) is elevated alongside the backup's activity, the bottleneck is disk contention, which nice alone will not fix; ionice is the relevant tool.
Apply both CPU and I/O deprioritization to the backup's process group:
backup_pid=$(pgrep -f 'full-backup.sh' | head -1)
renice -n 19 -p "$backup_pid"
ionice -c 3 -p "$backup_pid"
Verify:
Better long-term fix. Rather than manually renicing the process after the fact each night, edit the backup's own launch — its cron entry or systemd unit — to start with the correct priority from the beginning:
Conclusion. If web server latency during the backup window improves after this change, the original problem was CPU and/or I/O contention from the backup competing on equal footing with the web server; deprioritizing it at the source (the launch command, not a manual renice after the fact) prevents the same complaint from recurring on every future run. If latency does not improve, the bottleneck lies elsewhere — network, database query time, or application-level contention — and priority adjustment was the wrong tool for this particular symptom.
Common Mistakes
Expecting nice to Cap CPU Usage on an Idle System
A niced-down process still gets 100% of a core if nothing else wants it at that moment. Nice values only matter under contention; they are not a CPU quota mechanism (cgroups CPU limits, covered elsewhere in production topics, are the actual quota tool).
Confusing NI and PRI
PRI's numeric convention (and its exact range) is not the same as NI's -20 to 19 scale. Always set and reason about priority using NI, and treat PRI purely as a kernel-internal, derived display value.
Using Real-Time Scheduling as a Stronger nice
SCHED_FIFO/SCHED_RR are not "extra-negative nice values" — they preempt the entire normal scheduling class, including processes a nice value of -20 would still have to share time with. Reach for real-time scheduling only for workloads that genuinely require hard latency guarantees, and only after testing on a disposable system.
Deprioritizing CPU but Forgetting I/O
A batch job renamed to a favorable nice value can still stall interactive work through disk contention alone. Pair renice with ionice for any background job that does meaningful file I/O.
Renicing Only the Parent Process of a Job
A shell script's own priority change does not automatically propagate to every child process it later spawns unless the script itself was launched with the desired nice value, or renice -p/-g is applied to the whole process group. Verify with ps --forest that every relevant child actually inherited the intended priority.
Exercises
- Start two CPU-bound loops (
yes > /dev/null &twice) at nice values0and19respectively, then watchtop's%CPUcolumn for both. Explain the ratio you observe in terms of scheduling weight, not a fixed percentage split. - Use
renice -n 10 -u <your-username>against your own current shell session and explain, usingps -o ni, what did and did not change, and why lowering it back below0afterward would fail withoutsudo. - Write the single command that starts a hypothetical
nightly-export.shscript with both the lowest CPU priority and idle I/O class from the moment it launches, rather than adjusting it after the fact. - Explain, without running it, why setting
chrt -f -p 99on a poorly-written service with an infinite loop containing no blocking calls is more dangerous than settingrenice -n -20on the same service.
Verification criterion: any claim that "niceing a process limits its CPU usage" is incorrect and should not appear in your answers — the correct framing is always relative priority under contention, not an absolute limit.
References
- Linux man-pages:
nice(1),renice(1): https://man7.org/linux/man-pages/man1/nice.1.html - Linux man-pages:
sched(7): https://man7.org/linux/man-pages/man7/sched.7.html - Linux man-pages:
chrt(1): https://man7.org/linux/man-pages/man1/chrt.1.html - Linux man-pages:
ionice(1): https://man7.org/linux/man-pages/man1/ionice.1.html - Linux kernel documentation: CFS scheduler design: https://www.kernel.org/doc/html/latest/scheduler/sched-design-CFS.html
- Linux kernel documentation: real-time scheduling: https://www.kernel.org/doc/html/latest/scheduler/sched-rt-group.html
- Linux man-pages:
setpriority(2),getpriority(2): https://man7.org/linux/man-pages/man2/setpriority.2.html