How to Use cgroups v2 to Enforce Memory and CPU Resource Limits in Linux Terminal

When running multiple background compilation tasks, microservice containers, or developer databases on a Linux workstation or server, a single runaway process can consume 100% of physical RAM and trigger the Linux Out-Of-Memory (OOM) killer. While legacy Unix tools like ulimit restrict per-shell boundaries, modern Linux resource governance relies entirely on Control Groups version 2 (cgroups v2). Unified natively under the /sys/fs/cgroup virtual filesystem, cgroups v2 allows administrators to set hard and high memory ceilings, CPU weight throttling, and I/O bandwidth limits across process trees.

Understanding the cgroups v2 Unified Hierarchy

Unlike cgroups v1, which maintained disconnected hierarchies for each resource controller, cgroups v2 implements a strictly unified tree:

  • Single Hierarchy: Every process belongs to exactly one cgroup. Resource controllers (CPU, memory, IO, pids) are enabled hierarchically from root to leaf nodes.
  • No Internal Processes: In cgroups v2, a parent cgroup containing child cgroups cannot host processes directly, preventing controller delegation ambiguities.
  • Pressure Stall Information (PSI): Integrated kernel sensors that measure exact CPU, memory, and I/O stall times, signaling when workloads begin starving for hardware resources.

Checking cgroups v2 Availability

Modern Linux distributions (such as Ubuntu 22.04 LTS and 24.04 LTS, Debian 12, and Fedora) enable cgroups v2 by default. Confirm unified hierarchy support:

stat -fc %T /sys/fs/cgroup

If the command outputs cgroup2fs, your system is running native cgroups v2.

Inspect available controllers enabled on the root cgroup:

cat /sys/fs/cgroup/cgroup.controllers

Typical controllers include cpuset cpu io memory pids.

Creating a Custom cgroup and Setting Memory Quotas

Because cgroups operate as a virtual filesystem, creating a group is as simple as creating a standard directory:

# Create a dedicated group named 'sandbox_worker'
sudo mkdir -p /sys/fs/cgroup/sandbox_worker

# Set a high memory throttling limit (2 GB) and an absolute hard ceiling (2.5 GB)
echo "2G" | sudo tee /sys/fs/cgroup/sandbox_worker/memory.high
echo "2500M" | sudo tee /sys/fs/cgroup/sandbox_worker/memory.max

# Restrict swap space consumption to 512 MB
echo "512M" | sudo tee /sys/fs/cgroup/sandbox_worker/memory.swap.max

When a process inside this group reaches memory.high, the kernel actively slows its allocations and reclaims page cache without killing the process. If it exceeds memory.max, the OOM killer terminates processes strictly within that cgroup, leaving the rest of the operating system completely untouched.

Configuring CPU Quotas and Weights

To prevent a task from monopolizing CPU cores:

Option 1: Proportional Weight Balancing: Assign relative CPU priority (range: 1 to 10000, default: 100):

echo "50" | sudo tee /sys/fs/cgroup/sandbox_worker/cpu.weight

Option 2: Hard CPU Bandwidth Caps: Limit a process to 1.5 CPU cores (150,000 microseconds per 100,000 microsecond period):

echo "150000 100000" | sudo tee /sys/fs/cgroup/sandbox_worker/cpu.max

Assigning Active Processes to a cgroup

To move a running process into your custom group, write its Process ID (PID) into cgroup.procs:

echo 18429 | sudo tee /sys/fs/cgroup/sandbox_worker/cgroup.procs

All child threads and subsequently spawned sub-processes automatically inherit the cgroup constraints.

Managing Transient Limits via systemd-run

Rather than manually manipulating the /sys/fs/cgroup filesystem, systemd provides the systemd-run wrapper to launch transient services with instant cgroups v2 boundaries:

systemd-run --user --scope -p MemoryMax=1G -p CPUQuota=50% python3 intensive_script.py

This command spawns the script inside an isolated cgroup slice, enforcing a 1 GB memory cap and a 50% single-core CPU ceiling, cleaning up the cgroup automatically when execution terminates.

Get the best tech tips delivered straight to your inbox.

Join thousands of readers mastering Apple, Google, Microsoft, and Linux.