
September 11, 2026
12 min read
By Kokil Thapa | Last reviewed: September 2026
Linux cgroups v2 Explained starts with a simple idea: the kernel groups processes and applies resource limits to the group, not to each PID individually. On a production Ubuntu server running PHP-FPM, MySQL, Redis, and queue workers, one runaway job can starve everything else. Cgroups give you a kernel-level fence around CPU time, memory, disk I/O, and network bandwidth. If you maintain Linux servers for web applications, cgroup v2 is no longer optional background knowledge. Ubuntu 22.04 and 24.04 enable it by default, systemd owns the hierarchy, and containers depend on it. This guide walks through how v2 works, how it differs from v1, and the commands you actually use when a site slows down after deploy.
/sys/fs/cgroup/, and systemd creates slices automatically for services, scopes, and containers.What is Linux cgroups v2 and how does it differ from v1?
Cgroups — control groups — are a Linux kernel feature for grouping tasks and applying resource policies. Version 1 shipped multiple independent hierarchies. CPU might live under /sys/fs/cgroup/cpu while memory lived under /sys/fs/cgroup/memory. The same process could appear in several trees at once. That design worked for years, but it created edge cases around delegation, accounting, and container runtimes.
Cgroup v2 replaces that with a single unified hierarchy mounted at /sys/fs/cgroup. Every process sits in exactly one cgroup. Controllers — cpu, memory, io, pids, and others — attach to nodes in that one tree. Limits compose cleanly because parent cgroups constrain children. A child cannot exceed what its ancestors allow.
The practical differences matter on real servers. Cgroup v2 uses delegation through cgroup.subtree_control instead of ad-hoc v1 mount options. Memory accounting includes swap and kernel memory in clearer files. The io controller replaces the old blkio controller with a unified model. Pressure stall information — PSI — exposes CPU, memory, and I/O pressure at the cgroup level. That helps you spot contention before OOM kills arrive.
| Feature | cgroups v1 | cgroups v2 |
|---|---|---|
| Hierarchy | Multiple independent trees | Single unified tree |
| Process membership | Can differ per controller | Exactly one cgroup per process |
| Delegation | Complex mount rules | cgroup.subtree_control file |
| Default on Ubuntu 24.04 | Hybrid fallback possible | Unified v2 primary |
| systemd integration | Mixed v1/v2 on older releases | Native v2 slices and scopes |
| Pressure metrics | Limited | PSI files per resource |
On older CentOS 7 or legacy VPS images, you may still see hybrid mode. Modern Rocky Linux and AlmaLinux servers follow the same v2-first path as Ubuntu. When you migrate from CentOS to Rocky Linux, expect cgroup layout changes in your monitoring scripts.
How do you enable and verify cgroup v2 on Ubuntu?
Most fresh Ubuntu 22.04 and 24.04 installs already mount cgroup v2. Verification takes thirty seconds. Run these commands on any server you manage:
mount | grep cgroup
stat -fc %T /sys/fs/cgroup/
cat /proc/filesystems | grep cgroup If stat prints cgroup2fs, the unified hierarchy is active. You should see a single cgroup2 mount at /sys/fs/cgroup. A cgroup.controllers file at the root confirms available controllers:
cat /sys/fs/cgroup/cgroup.controllers
cat /sys/fs/cgroup/cgroup.subtree_control Typical output lists cpuset cpu io memory pids and possibly hugetlb rdma misc. If your kernel still boots in hybrid mode, add the kernel parameter systemd.unified_cgroup_hierarchy=1 to GRUB and reboot during a maintenance window.
Check whether a specific service runs under v2
systemd exposes the cgroup path for every unit. This is the fastest way to connect abstract docs to a running PHP-FPM or MySQL service:
systemctl show php8.3-fpm.service -p ControlGroup
systemctl show mysql.service -p ControlGroup
cat /proc/self/cgroup Under v2, /proc/self/cgroup shows a single line starting with 0:: followed by the cgroup path. Under legacy v1 you would see multiple lines with different controller prefixes.
How do you limit CPU and memory with cgroup v2?
Manual cgroup management teaches you what systemd automates. Create a test cgroup, enable controllers, move a process, and write limits. Never experiment on production paths without a rollback plan.
- Create a directory under the unified mount — the kernel creates a cgroup automatically.
- Enable controllers in
cgroup.subtree_controlon the parent. - Write the target PID to
cgroup.procs. - Set limit files on the child cgroup.
sudo mkdir /sys/fs/cgroup/myapp
echo "+cpu +memory" | sudo tee /sys/fs/cgroup/cgroup.subtree_control
echo $$ | sudo tee /sys/fs/cgroup/myapp/cgroup.procs
echo "50000 100000" | sudo tee /sys/fs/cgroup/myapp/cpu.max
echo "512M" | sudo tee /sys/fs/cgroup/myapp/memory.max The cpu.max format is $QUOTA $PERIOD in microseconds. The example above allows 50 ms of CPU time per 100 ms wall-clock window — effectively half a CPU core. Use max as the quota for unlimited CPU within parent bounds.
Memory limits and OOM behaviour
memory.max sets a hard ceiling. When the cgroup exceeds it, the kernel triggers an OOM kill inside that group. The process dies, but sibling cgroups keep running. That isolation saved a shared EC2 host I maintain where a Laravel queue worker leaked memory after a bad deploy.
Related files worth knowing:
memory.high— soft throttle before hard limitmemory.swap.max— cap swap usage for the groupmemory.current— live usage countermemory.events— OOM and high-watermark event counts
Pair cgroup memory limits with swap and memory tuning at the host level. Cgroups fence workloads; they do not replace adequate RAM. When you diagnose high memory usage, check both memory.current and application-level metrics.
I/O limits with io.max
The v2 io controller uses io.max with a format like 8:0 rbps=1048576 wbps=524288 where 8:0 is the major:minor device number from lsblk. This protects database disks when a backup job and a web app share the same VPS — a common setup on budget Nepali hosting at Rs 1,500–3,000/month (~USD 11–22).
How does systemd use cgroup v2 on modern Linux?
On current Ubuntu and Rocky systems, systemd is the cgroup manager. You rarely write to /sys/fs/cgroup by hand. Instead you declare limits in unit files or drop-ins. systemd translates them into cgroup v2 files at service start.
Every unit lands in a slice. Slices form a tree:
-.slice— root of the systemsystem.slice— system services like nginx, mysql, php-fpmuser.slice— user sessions and user servicesmachine.slice— virtual machines and containers
A typical drop-in for a memory-hungry queue worker on a Laravel app:
sudo systemctl edit laravel-worker.service [Service]
CPUQuota=30%
MemoryMax=512M
MemoryHigh=450M
IOWeight=50 Reload and restart:
sudo systemctl daemon-reload
sudo systemctl restart laravel-worker.service
systemctl show laravel-worker.service -p CPUUsageNSec -p MemoryCurrent -p ControlGroup This pattern appears across sister sites I deploy with GitLab CI and Deployer 7. One noisy worker must not take down PHP-FPM pools serving live traffic. Read the full systemd service management guide for timer units, socket activation, and dependency ordering alongside cgroup limits.
Scopes differ from services. A service is started by systemd; a scope wraps externally spawned processes. systemd-run creates transient scopes with limits — useful for ad-hoc artisan commands or database exports:
systemd-run --scope -p MemoryMax=256M -p CPUQuota=20% \
php artisan queue:work --once Learn how scopes interact with process management and signals when you stop or restart grouped tasks.
How do containers and Docker use cgroup v2?
Container runtimes create a cgroup per container under system.slice or a dedicated runtime path. Docker, containerd, and Podman all write the same v2 files — memory.max, cpu.max, pids.max — that you would write manually. The runtime just automates lifecycle.
Docker flags map directly to cgroup v2 knobs:
docker run -d --name api \
--cpus="1.5" \
--memory="768m" \
--memory-swap="768m" \
--pids-limit=100 \
myapp:latest Inspect the live cgroup path:
docker inspect api --format '{{.HostConfig.CgroupParent}}'
cat /sys/fs/cgroup/system.slice/docker-*.scope/memory.current Kubernetes passes limits through the kubelet to the container runtime. On a single-node k3s VPS, misconfigured limits look like throttling, not application bugs. If you run KVM guests or Flatcar Container Linux, the same v2 rules apply inside each layer.
Official kernel documentation for the unified hierarchy lives at docs.kernel.org cgroup v2 admin guide. systemd’s cgroup interface is documented in systemd.resource-control(5). Docker publishes cgroup driver requirements in the Docker resource constraints documentation.
How do you troubleshoot cgroup v2 problems on a live server?
Most cgroup issues show up as throttling, unexpected OOM kills, or services that refuse to start after a limit change. Work through this checklist before you reboot.
Confirm the unit landed in the expected cgroup
systemd-cgls
systemd-cgtop
cat /sys/fs/cgroup/system.slice/nginx.service/cgroup.procs systemd-cgtop gives a live view similar to top, but grouped by cgroup. It pairs well with Netdata monitoring and alerts for long-term graphs.
Read pressure and event counters
cat /sys/fs/cgroup/system.slice/php8.3-fpm.service/memory.pressure
cat /sys/fs/cgroup/system.slice/php8.3-fpm.service/memory.events
cat /sys/fs/cgroup/system.slice/php8.3-fpm.service/cpu.stat Rising some or full pressure in memory.psi means the cgroup waits for pages. The oom_kill counter in memory.events confirms the kernel killed a process inside the group. Check application logs immediately after.
Common mistakes
- Setting
MemoryMaxtoo low for PHP-FPM — pools spawn multiple workers that share one cgroup - Forgetting
daemon-reloadafter editing unit drop-ins - Mixing v1 tools like
cgcreateon a pure v2 host - Expecting
ulimitto enforce memory — ulimits and cgroups are separate layers; see sysctl and ulimits tuning - Running backup tar jobs without I/O or CPU caps during peak traffic hours
On shared infrastructure for projects like Adventure Third Pole Trek, cgroup limits are part of the deploy checklist alongside PHP-FPM pool sizing and MySQL buffer tuning. Treat them as performance optimization infrastructure, not emergency-only patches.
If you parse cgroup stats into JSON for dashboards, validate output with the JSON formatter tool before feeding it to Grafana or a custom admin panel. For cron-driven maintenance jobs that spike disk I/O, combine cron scheduling best practices with IOWeight limits on the service unit.
Interview prep? Expect cgroup v2 questions alongside namespaces and systemd in any Linux DevOps interview set. Know the unified hierarchy rule, three limit files, and how Docker maps flags.
Key Takeaways
- Cgroup v2 uses one unified hierarchy — every process belongs to exactly one cgroup with all controllers attached at the same level.
- Verify v2 with
stat -fc %T /sys/fs/cgroup/; expectcgroup2fson Ubuntu 22.04 and 24.04. - Set limits via systemd unit drop-ins (
MemoryMax,CPUQuota,IOWeight) rather than hand-editing sysfs on production. - Read
memory.pressure,memory.events, andcpu.statbefore you blame application code for slowdowns. - Container runtimes and Docker use the same v2 files —
--memoryand--cpusmap directly to kernel limits. - Combine cgroup fences with host-level tuning, monitoring, and sensible PHP-FPM worker counts for Laravel stacks.
People Also Ask
Is cgroup v2 enabled by default on Ubuntu 24.04?
Yes. Ubuntu 24.04 LTS boots with the unified cgroup v2 hierarchy as the primary layout. Run stat -fc %T /sys/fs/cgroup/ to confirm. Some older cloud images may still use hybrid mode until you set systemd.unified_cgroup_hierarchy=1 and reboot.
What happens when a cgroup hits its memory limit?
The kernel triggers an OOM kill inside that cgroup only. Processes in other cgroups keep running. Check memory.events for the oom_kill counter and inspect service logs for the victim process. Increase MemoryMax or fix the leak based on what you find.
Can I use cgroups v1 and v2 at the same time?
Hybrid mode mounts both, but new development targets v2 exclusively. Docker, Kubernetes, and systemd assume v2 on current releases. Avoid building new tooling against v1 paths — they will break on fresh installs.
How does cgroup v2 relate to Linux namespaces?
Namespaces isolate what a process sees — PID tables, mount points, network stacks. Cgroups limit what a process consumes — CPU, memory, I/O. Containers combine both. Namespaces provide the box; cgroups set the weight and size budget inside it.
Put cgroup v2 to work on your stack
Linux cgroups v2 Explained is not abstract kernel trivia. It is the mechanism that keeps your web stack fair under load. Start by verifying v2 on each server, add systemd drop-ins for your heaviest services, and wire PSI metrics into your monitoring. On the next OOM or CPU spike, you will know exactly which unit to inspect instead of rebooting and hoping.
Need help sizing limits for a Laravel, WordPress, or custom API deployment on Ubuntu? See the server support and maintenance service or browse the project portfolio for production examples. For a full health check — cgroups, PHP-FPM, MySQL, and deploy pipeline — contact us and describe your current host layout.
Frequently Asked Questions
0 Comments
Leave a comment
Your email is not published. Comments appear once they have been read. Sign in to have your details filled in.

