Skip to content

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.

ps -eo pid,ni,pri,comm --sort=ni | head
    PID  NI PRI COMMAND
  22110 -10  30 postgres
   1842   0  20 nginx
  30044  10  10 backup.sh

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

nice -n 15 backup-script.sh

-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:

sudo nice -n -5 critical-batch.sh

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:

renice -n 10 -p 22110
22110 (process ID) old priority 0, new priority 10

-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.

renice -n 15 -u batch-runner

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 -p 50 22110
chrt -p 22110
pid 22110's current scheduling policy: SCHED_FIFO
pid 22110's current scheduling priority: 50

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 199, 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):

ionice -c 3 -p 22110
ionice -p 22110
idle

-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 07 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.

ionice -c 3 nice -n 19 tar -czf /backup/full-backup.tar.gz /srv/data

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:

ps -eo pid,ni,pcpu,pmem,etime,cmd | grep -E 'tar|backup'
top

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:

ps -o pid,ni,cmd -p "$backup_pid"
ionice -p "$backup_pid"
    PID  NI CMD
  30044  19 /usr/local/bin/full-backup.sh
idle

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:

ionice -c 3 nice -n 19 /usr/local/bin/full-backup.sh

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

  1. Start two CPU-bound loops (yes > /dev/null & twice) at nice values 0 and 19 respectively, then watch top's %CPU column for both. Explain the ratio you observe in terms of scheduling weight, not a fixed percentage split.
  2. Use renice -n 10 -u <your-username> against your own current shell session and explain, using ps -o ni, what did and did not change, and why lowering it back below 0 afterward would fail without sudo.
  3. Write the single command that starts a hypothetical nightly-export.sh script with both the lowest CPU priority and idle I/O class from the moment it launches, rather than adjusting it after the fact.
  4. Explain, without running it, why setting chrt -f -p 99 on a poorly-written service with an infinite loop containing no blocking calls is more dangerous than setting renice -n -20 on 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