Hardware FixRecommendedDevice not working? Your driver may be the problemCheck updates for common hardware issues.Fix DriversHalloween DealsAmazon USHalloween week deals: useful picks before month-endAmazon US: current deals, useful picks and tech finds.Check DealsSlow PC?RecommendedPC slow today? Run a repair scan before it gets worseResolve common Windows issues and optimize system performance.Scan Now×
FREEDOM251

Freedom251 › Blog › cgroup v2

How to Limit Process Resources with cgroups on Linux (cgroup v2)

By Freedom251 Team · · 13 min read

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Use systemd resource controls for services and one-off commands, or write to the cgroup v2 control files directly when systemd is not managing the workload. A CPU quota limits aggregate CPU time, MemoryMax= (or memory.max) sets a hard memory boundary that can lead to a cgroup OOM kill, and settings such as CPU weight, memory.high, I/O limits, and task limits provide softer forms of control. The procedure below shows how to identify the host’s cgroup hierarchy, apply limits safely, and verify what the kernel actually did.

What a cgroup controls

A control group, or cgroup, is a hierarchical group of processes with policies for distributing system resources. Every process belongs to a cgroup hierarchy, and child groups inherit constraints from their ancestors. The Linux kernel documentation describes a cgroup as “a mechanism to organize processes hierarchically and distribute system resources along the hierarchy in a controlled and configurable manner.”

As an Amazon Associate I earn from qualifying purchases.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

In cgroup v2, one unified hierarchy exposes controller files for CPU, memory, I/O, process counts, and other resources. Typical files include:

  • cpu.max for a hard CPU bandwidth quota.
  • cpu.weight for proportional CPU scheduling priority.
  • memory.max for a hard memory ceiling.
  • memory.high for reclaim pressure and throttling before a hard limit.
  • io.max for per-device I/O limits.
  • pids.max for the maximum number of processes and threads.

These controls are hierarchical. If a parent allows 1 GB of memory, a child configured for 2 GB still cannot use more than the parent’s effective allowance. The same principle applies to CPU, I/O, and process-count constraints.

Choose the management layer

Use one manager for each cgroup. On most modern Linux distributions, systemd owns the cgroup v2 hierarchy and creates a unit cgroup for each service, scope, slice, session, and user. Writing competing policies into the same group from a second manager can produce errors or settings that are overwritten. systemd’s cgroup interface requires each individual cgroup to have a single writer.

Approach Best for Control style Scope and observation
Transient systemd scope A command that should be limited for one run Properties such as CPUQuota= and MemoryMax= One command; inspect with systemctl status and cgroup files
Service unit properties Long-running daemons Hard caps, weights, protection, I/O, and task limits A named service; managed through systemctl
Direct cgroup v2 files Systems without systemd or specialized supervisors Kernel controller files such as cpu.max and memory.max A manually created hierarchy; inspect files and event counters
Container runtime or Kubernetes Pods and containers Runtime settings translated into cgroup policies Container or pod scope; verify runtime driver and host hierarchy

For a systemd host, start with a transient scope or a unit drop-in. Direct file manipulation is appropriate only when you control the hierarchy and understand who owns it.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Check that the host uses cgroup v2

Before applying a policy, identify the hierarchy and the controllers available on the host.

  1. Check the filesystem type mounted at /sys/fs/cgroup:
    stat -fc %T /sys/fs/cgroup

    A cgroup v2 host reports cgroup2fs. A different result usually indicates a legacy or hybrid setup, where the v2-only file names in this guide may not exist.

  2. List controllers available at the hierarchy root:
    cat /sys/fs/cgroup/cgroup.controllers

    Common entries include cpu, memory, io, and pids. Availability depends on the kernel configuration and how the hierarchy was delegated.

  3. Determine whether systemd owns the tree. On a systemd host, inspect the process and unit cgroup paths with:
    systemctl status example.service
    systemd-cgls

    systemd-cgls displays the hierarchy; systemd-cgtop provides a live resource view.

Do not enable controllers or move processes inside a systemd-owned group unless the operation is part of systemd’s delegation model. A policy applied in the wrong group may affect only a shell process, not the daemon you intended to constrain.

Limit a one-off command with systemd

A transient scope gives a command a cgroup without requiring a permanent unit file. The following command starts ./workload with a 50 percent CPU quota and a 1 GiB hard memory limit:

systemd-run --scope -p CPUQuota=50% -p MemoryMax=1G -- ./workload

CPUQuota=50% permits up to half of one CPU on average over the scheduler’s accounting periods. It does not pin the process to a core, select a CPU frequency, or guarantee response latency. A multithreaded workload can use more than half of one core at an instant, but its aggregate CPU time remains bounded by the quota.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

MemoryMax=1G maps to the cgroup v2 hard memory limit. When reclaim cannot bring usage below the boundary, the kernel can invoke the cgroup OOM killer. The process may terminate rather than merely slow down, so use this setting only when that failure mode is acceptable.

To give the scope a recognizable name and keep it attached to the terminal, use:

systemd-run --scope --unit=limited-workload.service 
  -p CPUQuota=50% 
  -p MemoryMax=1G 
  -- ./workload

After starting it, find the resulting unit and its cgroup path with:

systemctl status limited-workload.service
systemctl show limited-workload.service -p ControlGroup -p CPUQuotaPerSecUSec -p MemoryMax

A scope ends when its processes exit. For a repeatable workload, put the settings in a service unit instead.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Apply limits to a persistent systemd service

Create a drop-in so package upgrades do not replace the policy. For a service named example.service:

sudo systemctl edit example.service

Enter a drop-in containing only the controls you need:

[Service]
CPUQuota=50%
CPUWeight=200
MemoryMax=1G
IOReadBandwidthMax=/dev/nvme0n1 20M
TasksMax=256

Reload the unit and restart it:

sudo systemctl daemon-reload
sudo systemctl restart example.service

Each property addresses a different problem:

  • CPUQuota=50%: a hard bandwidth cap equal to half of one CPU. A value of 200% permits two CPUs’ worth of aggregate time, subject to parent limits.
  • CPUWeight=200: proportional priority when competing services are runnable. It favors this service relative to siblings; it does not reserve a fixed amount of CPU and does not stop the service from using idle capacity.
  • MemoryMax=1G: a hard memory ceiling with a possible cgroup OOM event when the limit cannot be met.
  • IOReadBandwidthMax=/dev/nvme0n1 20M: caps read bandwidth for the specified block device. Use the device node that actually backs the workload; a limit on the wrong device has no useful effect.
  • TasksMax=256: limits the number of processes and threads in the unit, helping prevent a fork or thread explosion.

Do not combine a quota and a weight merely because both mention CPU. Use the quota when the requirement is “never exceed this aggregate bandwidth.” Use the weight when the requirement is “receive a larger or smaller share when there is contention.” You can use both when a service needs a ceiling as well as a relative priority below that ceiling.

Hard memory limits versus memory pressure

MemoryMax= and memory.max

MemoryMax=1G in systemd writes the equivalent cgroup v2 hard limit. Memory accounting includes major user-space allocations, page cache, and several kernel structures; the exact set can expand as kernel accounting coverage changes. When usage reaches the boundary, the kernel attempts reclaim. If it cannot reduce usage, a cgroup-scoped OOM action can kill a process in that group.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

The boundary is not an instantaneous byte-for-byte fuse. The kernel documentation notes that memory.max can be exceeded temporarily in some circumstances and that not every allocation path invokes the OOM killer. Treat it as a control boundary with an observable failure path, not as a guarantee that every allocation stops at exactly the displayed number.

MemoryHigh= and memory.high

memory.high is a throttling and reclaim boundary. Crossing it applies memory pressure and can slow the cgroup while the kernel reclaims pages; it does not directly invoke the cgroup OOM killer. A common staged policy is to set MemoryHigh=768M to start pressure and MemoryMax=1G as the final failure boundary.

For a service drop-in:

[Service]
MemoryHigh=768M
MemoryMax=1G

Choose the values from the workload’s normal working set and its acceptable degradation point. A high limit that is too close to the hard maximum leaves little room for reclaim; a maximum that is too low causes avoidable OOM kills during legitimate bursts.

Use CPU controls correctly

Quota is time bandwidth

In the kernel interface, cpu.max contains a quota and period. The equivalent systemd property, CPUQuota=, expresses the quota as a percentage of one CPU. CPU quota limits aggregate scheduling time over periods; it does not lock a process to a particular core, lower its frequency, or guarantee a latency target. A throttled service can experience pauses even when its average CPU use appears acceptable.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Weight is proportional sharing

CPUWeight= maps to the cgroup’s proportional CPU weight. It matters only when sibling groups compete for CPU. A higher weight increases the share under contention, while an idle machine may let every group run at full speed. Weight is therefore suitable for prioritization, not for a strict upper bound.

Inspect actual scheduling and throttling data with:

systemctl show example.service -p ControlGroup
cat /sys/fs/cgroup/system.slice/example.service/cpu.stat

The exact path varies by unit and slice. cpu.stat exposes usage and throttling counters that reveal whether a quota is being hit rather than merely configured.

Limit I/O and process creation

Block I/O

Systemd’s IOReadBandwidthMax= and related properties write cgroup I/O policies. The direct v2 interface is io.max. A device-specific policy must name the block device that carries the workload’s traffic. Storage stacks, encryption, and virtual devices can make the apparent device different from the physical medium, so verify the path before relying on the limit.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
cat /sys/fs/cgroup/system.slice/example.service/io.stat

io.stat reports per-device activity and is the primary check that the workload is using the cgroup you configured.

Process and thread count

TasksMax=256 maps to pids.max. It counts tasks, including threads, not just traditional process IDs. A service that creates many worker threads can reach the limit even when its process count looks small. Check current usage with:

systemctl show example.service -p TasksCurrent -p TasksMax
cat /sys/fs/cgroup/system.slice/example.service/pids.current

When the limit is reached, new process or thread creation fails. Set a value above the service’s normal peak and test startup paths that create workers dynamically.

Configure cgroup v2 files directly

Use this method only when systemd or another supervisor is not the cgroup manager. The example creates a child group named limited below the current cgroup root. Run it as root or with equivalent delegated permissions.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
  1. Create the child cgroup:
    sudo mkdir /sys/fs/cgroup/limited
  2. Enable the controllers in the parent’s subtree. The controller names must appear in cgroup.controllers:
    echo '+cpu +memory +io +pids' | sudo tee /sys/fs/cgroup/cgroup.subtree_control

    If a controller is unavailable, remove it from the command rather than assuming the kernel supports it.

  3. Set the child’s limits. For CPU, 50000 100000 means 50,000 microseconds of CPU time in a 100,000-microsecond period:
    echo '50000 100000' | sudo tee /sys/fs/cgroup/limited/cpu.max
    echo '1G' | sudo tee /sys/fs/cgroup/limited/memory.max
    echo '768M' | sudo tee /sys/fs/cgroup/limited/memory.high
    echo '256' | sudo tee /sys/fs/cgroup/limited/pids.max
  4. Apply an I/O policy using the device’s major and minor numbers. For example, first obtain them with lsblk, then write a device-specific rule in the form required by io.max:
    echo 'MAJOR:MINOR rbps=20971520' | sudo tee /sys/fs/cgroup/limited/io.max

    Replace MAJOR:MINOR with the real values; do not write the literal placeholder.

  5. Move the workload into the group by writing its PID to cgroup.procs:
    echo 12345 | sudo tee /sys/fs/cgroup/limited/cgroup.procs

    Move the process before it creates children if you need those descendants to remain in the same group. A process can be moved only where the hierarchy’s delegation and permissions allow.

The parent must enable a controller before the child receives its interface file. A child also cannot escape limits imposed by ancestors. If a manager owns the parent, configure the manager rather than changing these files underneath it.

Verify limits, usage, and failure events

Configuration files show requested values; event and usage files show behavior. For a service, first obtain its cgroup path:

systemctl show example.service -p ControlGroup

Then inspect the corresponding directory below /sys/fs/cgroup. Useful files include:

  • cpu.stat: CPU usage, periods, and throttling counters.
  • memory.current: current accounted memory.
  • memory.events: counters such as high-pressure and maximum-limit events, including OOM activity.
  • memory.stat: a breakdown of memory categories.
  • io.stat: per-device I/O usage.
  • pids.current: tasks currently in the group.
  • pids.events: process-count limit events.

For a live overview of systemd units, run:

systemd-cgtop

Compare observed counters with the policy. A rising CPU throttling counter means the quota is actively limiting work. A rising memory.events maximum or OOM counter means the memory boundary is being reached, not merely that the value was written successfully.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Remove or change a policy safely

Systemd service

Edit the drop-in and remove the property, or reset it explicitly, then reload and restart:

sudo systemctl edit example.service
sudo systemctl daemon-reload
sudo systemctl restart example.service

Stopping a service does not necessarily remove a persistent drop-in; the file remains until you edit it again.

Transient scope

Stop the scope by unit name:

systemctl stop limited-workload.service

The cgroup disappears after its processes exit and systemd releases it.

Direct cgroup

Move or terminate all processes in the child before removing its directory:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
cat /sys/fs/cgroup/limited/cgroup.procs
sudo rmdir /sys/fs/cgroup/limited

A non-empty cgroup cannot be removed. Do not delete a directory belonging to a systemd unit; let systemd manage that lifecycle.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Troubleshoot common failures

The control file does not exist

Confirm that the host is using cgroup v2 and that the controller is enabled in the parent’s cgroup.subtree_control. A controller listed nowhere in cgroup.controllers is unavailable to that hierarchy, possibly because of kernel configuration or delegation.

Best Value
Sale
UNIX and Linux System Administration Handbook, 4th Edition
  • New
  • Mint Condition
  • Dispatch same day for order received before 12 noon
  • Guaranteed packaging
  • No quibbles returns

The service ignores the limit

Check the unit’s actual ControlGroup path and inspect that directory’s files. The process may have been started outside the service, placed in a different scope, or moved by a container runtime. Also check ancestor limits: a parent can make the effective allowance smaller than the unit’s configured value.

Memory use is lower than expected, but the process is killed

Look at memory.events and memory.stat. Accounting includes page cache and several kernel structures, not only anonymous heap memory. A burst can also reach the hard boundary before monitoring samples capture the peak. Raise MemoryMax=, add a lower MemoryHigh= for gradual pressure, or reduce the workload’s working set.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

CPU quota causes periodic stalls

Inspect cpu.stat for throttling. A quota is enforced over scheduling periods, so a burst followed by a throttled interval can create latency even when average utilization is modest. If the goal is fairness rather than a ceiling, replace or supplement the quota with an appropriate CPUWeight=.

Containers behave differently from host services

Kubernetes and other container runtimes translate pod or container CPU and memory settings into cgroup policies. Check whether the host uses cgroup v1 or v2, which runtime and cgroup driver are active, and which parent slice contains the container. Host-level ancestor limits can reduce the resources available to a pod, while a runtime may rewrite child settings.

Design a policy that matches the failure you want

Requirement Use What happens at contention
Never allow more than a defined CPU bandwidth CPUQuota= or cpu.max The group is throttled after consuming its period allowance
Give one service priority over siblings CPUWeight= or cpu.weight Relative share changes only while groups compete
Slow memory growth before an emergency MemoryHigh= or memory.high Reclaim pressure and throttling increase; no direct cgroup OOM action
Set a final memory boundary MemoryMax= or memory.max Reclaim is attempted; a cgroup OOM kill can follow
Prevent fork or thread storms TasksMax= or pids.max New task creation fails at the limit
Restrict storage throughput IOReadBandwidthMax= or io.max I/O is limited for the selected device and direction

Start with a measured baseline, apply one control at a time, and watch the relevant event counters during normal traffic and deliberate stress. Keep enough headroom for startup, cache warming, and short-lived bursts. Record the unit or cgroup path alongside the policy so an operator can verify that the intended processes are covered.

Relationship to containers and Linux versions

Container isolation is not separate from cgroups: runtimes use cgroups to enforce CPU, memory, process-count, and I/O settings for containers. A pod limit therefore depends on the host kernel’s controller support, hierarchy mode, delegation, and runtime configuration. When a container appears to exceed or fall short of its requested resources, inspect the host cgroup mode and the runtime’s selected driver before changing application settings.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

The Linux kernel man-pages project identifies cgroups v2 as official beginning with Linux 4.5; the exact controls available on a current machine still depend on its kernel configuration and manager. systemd has exposed interfaces for creating and managing labeled process groups since version 205, and modern distributions generally present those interfaces through unit properties rather than requiring applications to write kernel files themselves.

Practical checklist

  • Confirm stat -fc %T /sys/fs/cgroup reports cgroup2fs.
  • Check cgroup.controllers and enable only controllers that are available and delegated.
  • Identify the process’s real unit or cgroup path before writing a policy.
  • Use a transient systemd-run --scope for experiments and a service drop-in for persistent daemons.
  • Choose quota for a ceiling, weight for relative priority, memory.high for pressure, and memory.max for a hard boundary.
  • Verify cpu.stat, memory.current, memory.events, io.stat, and pids.current after deployment.
  • Check ancestor groups and container runtime settings whenever effective behavior differs from the unit’s configured value.

Frequently Asked Questions

Does CPUQuota=50% limit a process to half of a CPU core?

It limits aggregate CPU time to the equivalent of half of one CPU over scheduling periods. It does not pin the process to a core or guarantee latency, and short bursts can still occur before throttling.

Will MemoryMax= immediately kill a process when it reaches the displayed number?

No. The kernel first attempts reclaim, and the boundary can be exceeded temporarily in some circumstances. If usage cannot be reduced, a cgroup OOM action may kill a process in the group.

Can I manage the same cgroup with systemd and manual echo commands?

Do not do so. A cgroup v2 group should have one manager. Configure a systemd-owned group through unit properties, or use direct files only in a hierarchy you control and have delegated.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

cgroup v2cgroupsCPU limitsLinuxmemory limitsResource managementsystemd

Leave a Reply

Your email address will not be published. Required fields are marked *

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Recommended PC Tool
Recommended PC Tool
Crashes, No Sound, or Screen Glitches?Free driver scan
PC Slower Than It Used to Be?Free scan - under a minute

Two free Windows tools

One Free Minute Could Fix That PC

Before you go - each of these free tools takes about a minute and tackles what quietly slows a Windows PC down.

Special offer. View Outbyte info, uninstall instructions, EULA, and Privacy Policy.