DevOpsil

Linux Control Groups (cgroups V2): Limiting CPU And Memory For Production Workloads

Aareez AsifAareez Asif4 min read

Linux Control Groups (cgroups v2): CPU & Memory Limits — Quick Reference

If you're running production workloads without cgroup constraints, you're one runaway process away from a bad day. Here's everything you need to configure cgroups v2 limits fast.

Verify You're on cgroups v2

# Check cgroup version
stat -fc %T /sys/fs/cgroup/
# Returns "cgroup2fs" = v2, "tmpfs" = v1

# Or check mount
mount | grep cgroup

If you're on a modern kernel (5.8+) with systemd 248+, you're almost certainly on v2. Good.


CPU Limits

Key Files

FilePurpose
cpu.maxHard CPU quota (period-based)
cpu.weightRelative CPU priority (default: 100)
cpu.pressurePSI metrics for CPU saturation

Set a CPU Quota (Hard Limit)

# Format: "quota period" (in microseconds)
# Limit to 50% of one CPU core:
echo "50000 100000" > /sys/fs/cgroup/myapp/cpu.max

# Limit to 2 full CPU cores:
echo "200000 100000" > /sys/fs/cgroup/myapp/cpu.max

# Remove limit entirely:
echo "max 100000" > /sys/fs/cgroup/myapp/cpu.max

Set CPU Weight (Soft Limit / Priority)

# Range: 1-10000, default is 100
# Give this cgroup 2x priority over defaults:
echo "200" > /sys/fs/cgroup/myapp/cpu.weight

# Deprioritize a batch job:
echo "10" > /sys/fs/cgroup/myapp/cpu.weight

Memory Limits

Key Files

FilePurpose
memory.maxHard limit — OOM kill above this
memory.highSoft limit — throttle before OOM
memory.minGuaranteed minimum (never reclaimed)
memory.currentCurrent usage (read-only)
memory.eventsOOM event counters

Set Memory Limits

# Hard limit at 512MB (OOM kill if exceeded):
echo "536870912" > /sys/fs/cgroup/myapp/memory.max
# Or use human-readable via systemd (see below)

# Soft limit at 400MB (throttle, swap before OOM):
echo "419430400" > /sys/fs/cgroup/myapp/memory.high

# Guarantee 128MB is never reclaimed from this cgroup:
echo "134217728" > /sys/fs/cgroup/myapp/memory.min

Monitor Memory Usage

# Current memory consumption:
cat /sys/fs/cgroup/myapp/memory.current

# Check if OOM events have occurred:
cat /sys/fs/cgroup/myapp/memory.events
# Look for: oom_kill > 0 = you have a problem

# Detailed memory breakdown:
cat /sys/fs/cgroup/myapp/memory.stat

Create and Manage a cgroup

# Create a new cgroup
mkdir /sys/fs/cgroup/myapp

# Add a process (PID) to the cgroup
echo <PID> > /sys/fs/cgroup/myapp/cgroup.procs

# List all processes in the cgroup
cat /sys/fs/cgroup/myapp/cgroup.procs

# Run a command directly in a cgroup (systemd-run):
systemd-run --scope -p CPUQuota=50% -p MemoryMax=512M mycommand

# Delete a cgroup (must be empty first)
rmdir /sys/fs/cgroup/myapp

systemd Slice/Service Limits (The Right Way in Production)

Direct filesystem manipulation doesn't survive reboots. Use systemd for persistent configs.

# /etc/systemd/system/myapp.service
[Service]
CPUQuota=150%          # 1.5 cores
CPUWeight=200          # 2x default priority
MemoryMax=1G           # Hard limit
MemoryHigh=768M        # Soft limit / throttle point
MemoryMin=256M         # Guaranteed minimum
MemorySwapMax=0        # Disable swap for this unit
# Apply changes without restart:
systemctl daemon-reload
systemctl restart myapp

# Check effective limits:
systemctl show myapp | grep -E "Memory|CPU"

# Real-time cgroup resource usage:
systemd-cgtop

Pressure Stall Information (PSI) — Don't Ignore This

PSI is cgroups v2's killer feature. It tells you how much a resource is contended.

# CPU pressure for a cgroup:
cat /sys/fs/cgroup/myapp/cpu.pressure
# some avg10=0.00 avg60=0.23 avg300=0.15 total=12345

# Memory pressure:
cat /sys/fs/cgroup/myapp/memory.pressure

# "some" = at least one task was stalled
# "full" = ALL tasks were stalled (critical)
# avg10/60/300 = 10s, 60s, 5min rolling averages

Rule of thumb: full avg60 > 5% on memory pressure means you need to increase the memory limit or fix a leak.


Quick Troubleshooting Commands

# Find which cgroup a process belongs to:
cat /proc/<PID>/cgroup

# View cgroup hierarchy:
systemd-cgls

# Check kernel OOM kill logs:
dmesg | grep -i "oom\|killed process"

# Watch live resource usage across all cgroups:
systemd-cgtop -d 1

# Verify cgroup controllers are available:
cat /sys/fs/cgroup/cgroup.controllers
# Should show: cpuset cpu io memory hugetlb pids rdma

The Numbers That Matter

ScenarioRecommended Setting
Latency-sensitive APIMemoryHigh = 80% of MemoryMax
Batch/background jobCPUWeight=10, MemorySwapMax=unlimited
Multi-tenant nodeAlways set both memory.max AND memory.high
No swap toleranceMemorySwapMax=0 explicitly

Bottom line: Set memory.high as your early-warning tripwire and memory.max as the hard stop. For CPU, prefer cpu.weight for general workloads and reserve cpu.max for strict SLA enforcement. And watch your PSI metrics — they'll tell you when limits are causing real pain before your users do.

Share:

Was this article helpful?

Aareez Asif
Aareez Asif

Senior Kubernetes Architect

10+ years orchestrating containers in production. Battle-tested opinions on everything from pod scheduling to service mesh. I've seen clusters burn and helped rebuild them better.

Related Articles

LinuxQuick RefBeginnerNeeds Review

Linux Networking Commands: Cheat Sheet

Linux networking commands cheat sheet for troubleshooting — interfaces, routing, DNS lookups, connections, iptables firewalls, and tcpdump packet capture.

Aareez Asif·
3 min read

More in Linux

View all →

Discussion